进程池,线程池
以时间换空间,控制进程,线程开启的数量
进程池与cpu一一对应是并行
线程池是并发:一个容器,这个容器限制住你开启线程(进程)的数量,比如4个,第一次肯定只能变更发的处理4个任务,只要有任务完成,线程马上就会接下一个人任务
以时间换空间,控制进程,线程开启的数量
进程池与cpu一一对应是(并行)或并行加并发
线程池是并发:一个容器,这个容器限制住你开启线程(进程)的数量,比如4个,第一次肯定只能变更发的处理4个任务,只要有任务完成,线程马上就会接下一个人任务
以时间换空间
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
def fun():
print(1)
t=ThreadPoolExecutor()#实例化一个进程池对象
t.submit(fun)#用submit开启一个进程池
基于线程池的服务端
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import socket
import os
import time
import random
def communicate(conn, addr):
while 1:
#
# try:
from_client_data = conn.recv(1024)
print(f'来自客户端{addr[1]}的消息: {from_client_data.decode("utf-8")}')
to_client_data = input('>>>').strip()
conn.send(to_client_data.encode('utf-8'))
if __name__ == '__main__':
server = socket.socket()
server.bind(('127.0.0.1', 8848))
server.listen(5)
print(1)
while 1:
con, addr = server.accept()
t=ThreadPoolExecutor()
t.submit(communicate,con,addr)
基于进程的服务端
import socket
from threading import Thread
import time
import random
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from multiprocessing import Process
def comniate(conn,addr):
while 1:
time.sleep(random.randint(1,3))
for_client_data=conn.recv(1024)
print(f'客户端{addr[1]}消息{for_client_data.decode("utf-8")}')
time.sleep(random.randint(3,4))
# to_client_data=input('>>>').strip()#不能使用input
conn.send('121212'.encode('utf-8'))
conn.close()
def func():
server=socket.socket()
server.bind(('127.0.0.1',8848))
server.listen(5)
print('开启')
while 1:
conn,addr=server.accept()
t.submit(comniate,conn,addr)
if __name__ == '__main__':
t = ProcessPoolExecutor(3)
func()
阻塞非阻塞
阻塞,非阻塞程序两种运行状态
三种运行状态 运行,就绪,阻塞
遇到io操作就会发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且会立刻释放cpu资源(cpu切换到其他进程中),结束进入就绪态
非阻塞:没有遇到io操作,但是我通过某种手段,让cpu强行运行我的程序
(或者用某种手段让程序即使遇到io操作也不会停在原地,执行其他操作,力求尽可能多的占有cpu)
同步与异步
obj=pool.submit()#obj是动态对象
打印obj出现状态
obj.result()等待这个任务结束,返回结果,在执行下一个任务(加上这个result会变成同步)#需要等到拿到返回结果
提交任务的两种方式
同步调用:
提交完任务后(任务执行不完会被cpu会切走),(不管有计算还是遇到io)就在原地等待,直到任务运行完毕后,拿到任务的返回值(不一定有),才继续执行下一行代码(同步调用和阻塞没有关系)
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
import random
import os
def task(i):
print(f'{os.getpid()}开始任务')
time.sleep(random.randint(1,3))
print(f'{os.getpid()}任务结束')
return i
if __name__ == '__main__':
pool=ProcessPoolExecutor()
for i in range(20):
obj=pool.submit(task,i)#开启子进程
print(obj.result())#等待打印返回值#return 的返回值返回给了 result
pool.shutdown(wait=True)
print('===主')
多个任务,同步调用效率低
异步调用:
一次提交多个任务,然后直接执行下一行代码,
如何接收返回值
1.统一回收结果
假如异步发出10个任务,并发的执行,但是统一的接收所有的任务的返回值(效率低,不能实时的获取结果)
缺点:我不能马上收到任何一个已经完成的任务的返回值
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
import random
import os
def task(i):
print(f'{os.getpid()}开始任务')
time.sleep(random.randint(1,3))
print(f'{os.getpid()}任务结束')
return i
if __name__ == '__main__':
pool=ProcessPoolExecutor()
li=[]
for i in range(20):
obj=pool.submit(task,i)
li.append(obj)
# print(obj.result()) # 打印返回值
pool.shutdown(wait=True)
for i in li:
print(i.result())
print('===主')
2.完成一个任务接收一个返回结果(有问题)
import requests
from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import ThreadPoolExecutor
def task(url):
ret=requests.get(url)
if ret.status_code==200:
return parse(ret.text)
#parse(ret.text)#一个线程除了取数据还需要清洗数据 时间长
def parse(obj):
return(len(obj))
#def task(url):
#ret=requests.get(url)
#if ret.status_code==200:
#return parse(ret.text)
#def parse(obj):
#return(len(obj))
if __name__ == '__main__':
pool=ThreadPoolExecutor(4)
url_list=[
'https://www.luffycity.com/',
'https://www.baidu.com/',
'https://www.luffycity.com/',
'https://www.luffycity.com/',
'https://www.luffycity.com/',
'https://www.luffycity.com/',
'https://www.luffycity.com/',
]
li=[]
for url in url_list:
obj=pool.submit(task,url)
li.append(obj)
pool.shutdown(wait=True)
for i in li:
print(i.result())#统一接收结果
#两个函数耦合性太高
shutdown:
只有线程 进程池里面有
1.让我的主进程池中所有的子进程都结束任务之后,类似join,一个任务是通过一个函数实现的,任务完成了的返回值是函数的返回值
2.执行时不允许别人添加执行任务,接受返回值就证明任务结束了
浏览器的工作原理
向服务端发送一个请求,服务端验证你的请求,如果正确,给你的浏览器返回一个文件,将文件里面的代码渲染成你看到的漂亮美丽的模样.
爬虫
1.利用代码模拟一个浏览器,浏览器的工作流程
2.数据清洗(处理源代码)
对源代码进程数据 清晰得到我想要的数据
import requests
response=requests.get('http://www.baidu.com')#拿到一个网址url
if response.status_code==200: #200可以访问
print(response.text)#进行网页数据获取
异步+回调
运行完不等待返回值,返回值需统一回收,想要不统一,需要用到回调机制
进行
异步是io密集型,回调是计算密集型
异步统一回收,实时接收,回调接收处理结果
什么是回调函数?
按顺序接收每个任务的结果,进行下一步 处理.
import requests
from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import ThreadPoolExecutor
def task(url):
'''模拟的就是爬取多个源代码 一定有IO操作'''
ret=requests.get(url)
if ret.status_code==200:
return ret.text
def parse(obj):
'''模拟对数据进行分析 一般没有IO'''
print(len(obj.result()))
if __name__ == '__main__':
pool=ThreadPoolExecutor(4)
url_list=[
'https://www.luffycity.com/',
'https://www.baidu.com/',
'https://www.luffycity.com/',
'https://www.luffycity.com/',
'https://www.luffycity.com/',
'https://www.luffycity.com/',
'https://www.luffycity.com/',
]
for url in url_list:
obj=pool.submit(task,url)
obj.add_done_callback(parse)#回调函数
#线程池设置4个线程, 异步发起10个任务,每个任务是通过网页获取源码, 并发执行,
# 当一个任务完成之后,将parse这个分析代码的任务交由剩余的空闲的线程去执行,你这个线程继续去处理其他任务.
#进程池+回调是全部交由主进程进行运行
#线程池+回调是全部交由空闲线程运行
线程queue
十四 线程队列
线程之间的通信我们列表行不行呢,当然行,那么队列和列表有什么区别呢
queue队列 :使用import queue,用法与进程Queue一样
- class queue.Queue(maxsize=0) #先进先出
import queue #不需要通过threading模块里面导入,直接import queue就可以了,这是python自带的
#用法基本和我们进程multiprocess中的queue是一样的
q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')
# q.put_nowait() #没有数据就报错,可以通过try来搞
print(q.get())
print(q.get())
print(q.get())
# q.get_nowait() #没有数据就报错,可以通过try来搞
'''
结果(先进先出):
first
second
third
'''
先进先出示例代码
class queue.LifoQueue(maxsize=0) #last in fisrt out
import queue
q=queue.LifoQueue() #队列,类似于栈,先进后出的顺序
q.put('first')
q.put('second')
q.put('third')
# q.put_nowait()
print(q.get())
print(q.get())
print(q.get())
# q.get_nowait()
'''
结果(后进先出):
third
second
first
'''
先进后出示例代码
class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列
第三种 优先级队列 数字越低 优先级越高
import queue
q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((-10,'a'))
q.put((-5,'a')) #负数也可以
# q.put((20,'ws')) #如果两个值的优先级一样,那么按照后面的值的acsii码顺序来排序,如果字符串第一个数元素相同,比较第二个元素的acsii码顺序
# q.put((20,'wd'))
# q.put((20,{'a':11})) #TypeError: unorderable types: dict() < dict() 不能是字典
# q.put((20,('w',1))) #优先级相同的两个数据,他们后面的值必须是相同的数据类型才能比较,可以是元祖,也是通过元素的ascii码顺序来排序
q.put((20,'b'))
q.put((20,'a'))
q.put((0,'b'))
q.put((30,'c'))
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
'''
优先级队列示例代码
这三种队列都是线程安全的,不会出现多个线程抢占同一个资源或数据的情况。
多线程抢占资源,串行
第一种 先进先出
q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get(block=false))
q.get(timeout=2)
第二种后进先出 堆栈 lifo
事件event
进程使用标志位FALG=FALSE
开启两个线程,一个线程运行到中间的某个阶段,触发另 个线程执行.两个线程增加了耦合性.
event.wait()#等待
event.set()#ture
is_set==isSet#判断是否为ture 方法
多进程
from threading import Thread,current_thread,Event
from multiprocessing import Process,queues,Event
from concurrent.futures import ThreadPoolExecutor
import time
event=Event()
def check(q):
print('%s 正常检测服务是否正常。。。'% current_thread().name)
time.sleep(1)
q.set()
def connect(q):
num = 1
while not q.is_set():
if num==4:
print('%s 连接失败' % current_thread().name)
break
else:
print("%s 开始尝试连接%s次"%(current_thread().name,num))
q.wait(1)
num+=1
else:
print('%s 连接成功' % current_thread().name)
if __name__ == '__main__':
# q=queues.Queue()
event=Event()
t1=Process(target=connect,args=(event,))
t2=Process(target=connect,args=(event,))
t3=Process(target=connect,args=(event,))
T4=Process(target=check,args=(event,))
T4.start()
t1.start()
t2.start()
t3.start()
多线程
from threading import Thread,current_thread,Event
from multiprocessing import Process
from concurrent.futures import ThreadPoolExecutor
import time
event=Event()
def check():
print('%s 正常检测服务是否正常。。。'% current_thread().name)
time.sleep(3)
event.set()
def connect():
num = 1
while not event.is_set():
if num==4:
print('%s 连接失败' % current_thread().name)
break
else:
print("%s 开始尝试连接%s次"%(current_thread().name,num))
event.wait(2)
num+=1
else:
print('%s 连接成功' % current_thread().name)
if __name__ == '__main__':
t1=Thread(target=connect)
t2=Thread(target=connect)
t3=Thread(target=connect)
T4=Thread(target=check)
T4.start()
t1.start()
t2.start()
t3.start()
开启两个线程,一个线程允许到中间某个阶段,触发另一个线程执行
两个线程增加耦合性
协程(单线程下)
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。
python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
什么是协程?
单个线程并发的处理多个任务. 程序控制协程的切换+保持状态.
单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
并发
如何实现并发 多线程 多进程 并发去执行,携程也是并发
并行
多个cpu执行多个任务,4个cpu执行4个任务 速度快开销大需要开启4个进程
串行
一个线程执行一个任务,执行完毕之后,执行下一个任务
并发的核心:
切换并且保持状态
多线程并发
3个线程处理10个任务
多线程遇到阻塞执行下一个线程 操作系统强行把cpu切到下一个线程(例如三个线程,一个线程阻塞,下一个线程调用cpu,)
多线程切换是cpu在不同线程下的切换,cpu只能运行线程(不能主动切换),线程是最小的执行单位
协程(单线程下并发的操作流程)
单个线程并发的处理多个任务,程序控制协程的切换+保持状态
协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度
一个线程运行多个任务(单线程的并发)
协程和cpu的关系
cpu一直运行线程,当遇到阻塞,协程调用cpu执行下一个任务遇到,阻塞切换到下一个任务(遇到阻塞切换是线程在三个任务之间来回切换,(多线程是指cpu在不同线程之间切换),cpu一直运行线程,线程没有停
利用多线程io的阻塞时间 进行下一个线程的操作
10个任务,让你给我并发10个任务
协程最好
前提一个cpu
1.开启多进程(有主线程)并发执行。--操作系统--时刻监视cpu(cpu停了)遇到阻塞或者运行时间过长就把cpu切走)切换+保持状态
2.开启多线程并发执行 --操作系统--时刻监视cpu)遇到阻塞(同时阻塞)或者运行时间过长(操作系统)切换+保持状态
3.(一个线程并发执行任务)开启协程并发执行,自己的程序(代码)绑定cpu 让cpu一直工作,把控着cpu在3个任务之间来回切换+保持状态
对3详细解释:协程他切换速度非常快,蒙蔽操作系统的眼睛,让操作系统认为cpu一直在运行你这一个线程(协程)
协程的优点:
1.运行速度快,单线程内就可以实现并发效果
2.协程会长期霸占cpu只执行我的程序效率高
3.携程开销小,
4.单线程并发最大限度利用cpu
缺点
协程单线程本质上是单线程下的无法利用多核
(一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程)
协程指的是单个线程,一旦出现阻塞,将会阻塞整个线程 cpu被操作系统切走,整个线程结束
总结协程特点:
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁(因为是一个线程,多线程需要加锁,多个任务修改数据共享 )
3.用户程序里自己保存多个控制流的上下文栈(保持状态)
4一个协程遇到io操作自动切换到其他协程
并发的本质:切换+保持状态
协程处理io密集型,计算密集型,还剩串行好
携程一直阻塞会来回切 整个线程就会结束 ,操作系统会切走cpu,结束了
greenlet
模块里 有一个机制强行不让cpu切换
任务之间来回切 切换加保存状态 遇到io不会切换需要等
g2.switch()#切换保存状态
from greenlet import greenlet
def eat(name):
print('%s eat 1' %name) #2
g2.switch('taibai') #3
print('%s eat 2' %name) #6
g2.switch() #7
def play(name):
print('%s play 1' %name) #4
g1.switch() #5
print('%s play 2' %name) #8
g1=greenlet(eat)
g2=greenlet(play)
g1.switch('taibai')
Gevent
模块里 有一个机制强行不让cpu切换
spawn(cls, *args, **kwargs)
#传参依次是(函数,位置参数,默认参数)
g1=gevent.spawn(eat,'eg'(位置传参))#前面一个是函数,第二个参数是位置传参
g2=gevent.spawn(play,name='on'(默认传参))
import gevent#引用一个第三方模块
# from multiprocessing import Process
def eat(name):
print('%s eat 1' %name)
gevent.sleep(2)#这个阻塞是gevent的阻塞
print('%s eat 2' %name)
def play(name):
print('%s play 1' %name)
gevent.sleep(1)
print('%s play 2' %name)
g1=gevent.spawn(eat,'eg')#前面一个是函数,第二个参数是位置传参
g2=gevent.spawn(play,name='on')
'''
为什么加join就运行了
一个线程相当于虚拟了两个线程
因为主线程结束了 join限制主线程不结束
'''
g1.join()
g2.join()
#或者gevent.joinall([g1,g2])
print('主')
最终版本把下面所有任务的阻塞都打上标记
import gevent
from gevent import monkey
monkey.patch_all() # 一定要写到最上面打补丁: 将下面的所有的任务的阻塞都打上标记
def eat(name):
print('%s eat 1' %name)
time.sleep(2)#阻塞切换
print('%s eat 2' %name)
def play(name):
print('%s play 1' %name)
time.sleep(1)#阻塞一秒
print('%s play 2' %name)
g1 = gevent.spawn(eat,'egon')
g2 = gevent.spawn(play,name='egon')
# g1.join()
# g2.join()
gevent.joinall([g1,g2])
协程下面的两个任务阻塞,切换一两次就被操作系统讲cpu拿走了,当一个任务一个任务最先阻塞完毕之后,协程就会将cpu,抢回来执行