协程
进程
进程是计算机中最小的资源分配单位
进程之间数据隔离
进程的开启、销毁、切换的开销大
python中进程可以利用多核
multiprocess
Process
Lock——保证数据安全
Queue——IPC机制
Manager——数据共享
Pool——进程池
线程
线程是计算机中能被cpu调度的最小单位
线程之间数据共享
线程的开启、销毁、切换的开销小
线程是进程内部的一个执行单位
python中线程不能利用多核
threading
Thread
Lock——在同一个线程中,互斥锁只能连续acquire一次
Rlock——在同一个线程中,递归锁可以连续acquire多次
queue
队列——先进先出
栈——后进先出
优先级队列——优先级高的先出,一般元组的第一个元素是数字
concurrent.futures
进程池——ProcessPoolExecutor
定义的进程数:cpu个数/cpu个数+1
线程池——ThreadPoolExecutor
定义的线程数:cpu的个数*5
submit——提交任务
shutdown——相当于close,join
map——相当于submit+shutdown
result——获取结果
协程
关键点
1.掌握概念和基础用法
2.安装第三方模块
协程的概念
协程的本质是一条线程分成多份,每一份去执行一段代码
多端代码能够在一条线程之间来回切换
如果能够在一段代码执行遇到阻塞的过程中切换到另一段可以执行的代码上
相当于完成了利用协程更加充分利用线程的目的
线程已经是操作系统能“看到”的最小单位了,因此它感受不到协程的存在
协程利用切换来规避IO操作带来的好处是:
一条线程能够执行多个任务
减少了一条线程的阻塞,帮助线程在操作系统调度的时候多抢占cpu
协程由于操作系统不可见,所以协程的切换不是由操作系统控制的
而是由程序员控制,因为协程是用户级
协程之间永远数据安全,因为它只是在一条线程中进行任务的切换
def pro(): # 第三步 print(1) # 第五步 n = yield # 第六步 # 第十步 print(n) # 第十一步 yield "b" # 第十二步 def com(): g = pro() # 第二步 a = next(g) # 第四步 第七步 print(a) # 第八步 b = g.send(2) # 第九步 # 第十三步 print(b) # 第十四步 com() # 第一步 # 1 # None # 2 # b
#串行执行 import time def consumer(res): '''任务1:接收数据,处理数据''' pass def producer(): '''任务2:生产数据''' res=[] for i in range(10000000): res.append(i) return res start=time.time() #串行执行 res=producer() consumer(res) # 写成consumer(producer())会降低执行效率 stop=time.time() print(stop-start) # 0.9015903472900391
#基于yield并发执行 import time def consumer(): '''任务1:接收数据,处理数据''' while True: x=yield def producer(): '''任务2:生产数据''' g=consumer() next(g) for i in range(10000000): g.send(i) start=time.time() # 基于yield保存状态,实现两个任务直接来回切换,即并发的效果 # PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的. producer() stop=time.time() print(stop-start) # 0.8427042961120605 # 单纯的线程之间两个任务切换是会浪费时间的 # 如果数据量非常大,存储数据也需要时间 # 如果每一次切换都要记录当前的状态,在切换回来之后还要读取之前的状态
import time def pro(): # 第三步 print(1) # 第五步 n = yield # 第六步 # 第十步 time.sleep(1) # 第十一步 print(n) # 第十二步 yield "b" # 第十三步 def com(): g = pro() # 第二步 a = next(g) # 第四步 第七步 print(a) # 第八步 b = g.send(2) # 第九步 # 第十四步 print(b) # 第十五步 com() # 第一步 # 1 # None # 运行到这里后停顿1s再出下面的结果 # 2 # b
# 虽然yield能实现协程函数,但是不能规避io # gevent 规避io操作,判断程序中的io,遇到io就切换到另一个任务来执行 # greenlet 负责在两个任务之间切换的模块,状态的保存和读取 # 安装gevent # Pycharm中安装/cmd中安装 # 开发环境——写代码用的环境 # 测试环境——测试人员用的环境 # 生产环境——linux,写的代码实际对用户提供服务的时候使用的环境 import greenlet def eat(): print("eat1") print("eat2") def sleep(): print("sleep1") print("sleep2") g1 = greenlet.greenlet(eat) g2 = greenlet.greenlet(sleep) g1.switch() # eat1 # eat2
import greenlet def eat(): print("eat1") g2.switch() print("eat2") g2.switch() def sleep(): print("sleep1") g1.switch() print("sleep2") g1 = greenlet.greenlet(eat) g2 = greenlet.greenlet(sleep) g1.switch() # eat1 # sleep1 # eat2 # sleep2 # 灵活的记录和保存状态
import greenlet # 不规避io操作,只做记录和保留状态 import time def eat(): print("eat1") time.sleep(1) g2.switch() print("eat2") g2.switch() def sleep(): print("sleep1") g1.switch() time.sleep(1) print("sleep2") g1 = greenlet.greenlet(eat) g2 = greenlet.greenlet(sleep) g1.switch() # eat1 # sleep1 # eat2 # sleep2 # g1=gevent.spawn(func,1,,2,3,x=4,y=5) # # 创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat # # 后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的 # g2=gevent.spawn(func2) # g1.join() #等待g1结束 # g2.join() #等待g2结束 # #或者上述两步合作一步:gevent.joinall([g1,g2]) # # g1.value#拿到func1的返回值
import gevent import time def eat(): print("eat1") # time.sleep(1) # 这也有IO操作,但是没有进行切换,因为gevent不认识,因此可以换为gevent.sleep(1) gevent.sleep(1) print("eat2") def sleep(): print("sleep1") # time.sleep(1) gevent.sleep(1) print("sleep2") # eat() # sleep() # 这样会分别执行 g1 = gevent.spawn(eat) g2 = gevent.spawn(sleep) # 如果只是这样,运行没结果,只是写明这是协程任务 # 协程中的多个任务遇到IO才切换 gevent.sleep(2) # 检测到主程序有了IO操作就切换 # 现在开始运行就有结果了 # eat1 # eat2 # sleep1 # sleep2
import gevent import time def eat(): print("eat1") gevent.sleep(1) print("eat2") def sleep(): print("sleep1") gevent.sleep(1) print("sleep2") g1 = gevent.spawn(eat) g2 = gevent.spawn(sleep) gevent.joinall([g1, g2]) # eat1 # sleep1 # eat2 # sleep2
from gevent import monkey monkey.patch_all() # 必须写在下面两个模块之前 import gevent import time def eat(): print("eat1") time.sleep(1) print("eat2") def sleep(): print("sleep1") time.sleep(1) print("sleep2") g1 = gevent.spawn(eat) g2 = gevent.spawn(sleep) gevent.joinall([g1, g2]) # eat1 # sleep1 # eat2 # sleep2
# 协程的名字 from gevent import monkey monkey.patch_all() # 必须写在下面两个模块之前 import gevent import time from threading import currentThread def eat(): print("eat :", currentThread()) print("eat1") time.sleep(1) print("eat2") def sleep(): print("sleep :", currentThread()) print("sleep1") time.sleep(1) print("sleep2") g1 = gevent.spawn(eat) g2 = gevent.spawn(sleep) gevent.joinall([g1, g2]) # eat : <_DummyThread(DummyThread-1, started daemon 2396830611016)> # DummyThread-1 —— 虚拟线程名 # eat1 # sleep : <_DummyThread(DummyThread-2, started daemon 2396830612296)> # sleep1 # eat2 # sleep2
# 爬虫应用 # 网络IO,sleep操作 from gevent import monkey monkey.patch_all() import gevent import time from urllib import request def func(name, url): ret = request.urlopen(url) with open(name+".html", "wb") as f: f.write(ret.read()) url_list = [ ("python", "https://www.python.org/"), ("blog", "https://www.cnblogs.com/"), ("dou_ban", "https://www.douban.com/") ] start = time.time() g_l = [] for url_item in url_list: g = gevent.spawn(func, *url_item) g_l.append(g) gevent.joinall(g_l) end = time.time() print(end - start)
# 如果是打开一个已有内容的文件,速度更快,这里把之前的加上做对比 from gevent import monkey monkey.patch_all() import gevent import time from urllib import request def func(name, url): ret = request.urlopen(url) with open(name+".html", "wb") as f: f.write(ret.read()) url_list = [ ("python", "https://www.python.org/"), ("blog", "https://www.cnblogs.com/"), ("dou_ban", "https://www.douban.com/") ] start = time.time() g_l = [] for url_item in url_list: g = gevent.spawn(func, *url_item) g_l.append(g) gevent.joinall(g_l) end = time.time() print(end - start) # 这里是基于上面已经运行的情况下执行 def func(name, url): ret = request.urlopen(url) with open(name+".html", "wb") as f: f.write(ret.read()) url_list = [ ("python", "https://www.python.org/"), ("blog", "https://www.cnblogs.com/"), ("dou_ban", "https://www.douban.com/") ] start = time.time() g_l = [] for url_item in url_list: g = gevent.spawn(func, *url_item) g_l.append(g) gevent.joinall(g_l) end = time.time() print(end - start) # 7.841484546661377 # 3.320158004760742
# 使用并发实现socketserver # server.py # from gevent import socket # 可以这样 # 如果用原来的socket,就必须先这样: from gevent import monkey monkey.patch_all() import socket import gevent def talk(conn): while 1: msg = conn.recv(1024).decode("utf-8") conn.send(msg.upper().encode("utf-8")) sk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.listen() while 1: conn, addr = sk.accept() # 这里不能阻塞的同时又发送消息 # while 1: # conn.send() gevent.spawn(talk, conn)
# client.py import socket from threading import Thread def client() sk = socket.socket() sk.connect(("127.0.0.1", 8080)) while 1: sk.send(b"Hello") msg = sk.recv(1024) print(msg) for i in range(500): # 500个协程 一条线程顶500个线程的服务 Thread(target=client).start() # 进程的个数:cpu个数+1 # 线程的个数:cpu个数*5 # 协程的个数:500