单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。
1.协程
--本质上是一个线程
--能够在多个任务之间切换来节省一些IO时间
--协程中任务之间的切换也消耗时间,但是开销要远远小于进程线程之间的切换
2.用yield实现任务的切换
--yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
--send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换
import time def consumer(): while True: x = yield time.sleep(1) print('处理了数据 :',x) def producer(): c = consumer() next(c) for i in range(10): time.sleep(1) print('生产了数据 :',i) c.send(i) producer()
3.greenlet模块实现协程
from greenlet import greenlet def eat(): print('eating start') g2.switch() # 转换到函数play并保存执行进度,当再转换到此函数时,接着此处执行 print('eating end') g2.switch() def play(): print('playing start') g1.switch() print('playing end') g1 = greenlet(eat) # 将eat函数交给greenlet执行,赋给g1,当调用g1.switch()时就去执行eat函数 g2 = greenlet(play) g1.switch() # 执行函数eat """执行结果为: eating start playing start eating end playing end """
4. gevent模块实现协程
from gevent import monkey;monkey.patch_all() # 这句代码的作用就是让协程的这个模块认识其他模块中的IO操作,然后再执行过程中遇到IO操作进行跳转。必须在第一句 import gevent import time def eat(): print('eating start') time.sleep(1) print('eating end') def play(): print("playing start") time.sleep(1) print('playing end') g1 = gevent.spawn(eat) g2 = gevent.spawn(play) g1.join() g2.join() """执行结果为: eating start playing start eating end playing end """
# 进程和线程的任务切换由操作系统完成
# 协程任务之间的切换由程序(代码)完成,只有遇到协程模块能识别的IO操作的时候,程序才会进行任务切换,实现并发的效果
同步 和 异步 from gevent import monkey monkey.patch_all() import time import gevent def task(n): time.sleep(0.5) print(n) def sync(): for i in range(10): task(i) def async(): g_lst = [] for i in range(10): g = gevent.spawn(task,i) g_lst.append(g) gevent.joinall(g_lst) # for g in g_lst:g.join() sync() # 同步 async() # 异步
协程 : 能够在一个线程中实现并发效果的概念
能够规避一些任务中的IO操作
在任务的执行过程中,检测到IO就切换到其他任务
协程 在一个线程上 提高CPU 的利用率
协程相比于多线程的优势 切换的效率更快
# 爬虫的例子 # 请求过程中的IO等待 from gevent import monkey;monkey.patch_all() import gevent from urllib.request import urlopen # 内置的模块 def get_url(url): response = urlopen(url) content = response.read().decode('utf-8') return len(content) g1 = gevent.spawn(get_url,'http://www.baidu.com') g2 = gevent.spawn(get_url,'http://www.sogou.com') g3 = gevent.spawn(get_url,'http://www.taobao.com') g4 = gevent.spawn(get_url,'http://www.hao123.com') g5 = gevent.spawn(get_url,'http://www.cnblogs.com') gevent.joinall([g1,g2,g3,g4,g5]) print(g1.value) print(g2.value) print(g3.value) print(g4.value) print(g5.value) # 几乎同时得到输出结果
5.协程实现socketserver
server端
from gevent import monkey monkey.patch_all() import socket import gevent sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() def talk(conn): conn.send(b'hello') print(conn.recv(1024).decode('utf-8')) conn.close() while True: conn,addr = sk.accept() gevent.spawn(talk,conn) sk.close()
client端
import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) msg = sk.recv(1024).decode('utf-8') print(msg) info = input('>>>').encode('utf-8') sk.send(info) sk.close()