线程中的多个任务,其中的每一个任务都可以成为一个协程,每一个协程都可以在一条线程中任意的切换
原理:
多个线程处理多个任务就相当于把所有任务中的空隙都填满的一个单线程
优势:
1.不必做线程之间的切换了
2.能够让一个线程看起来尽量忙碌,骗过操作系统,让程序不进入阻塞队列
3.线程是操作系统中能被CPU调度的最小单位,操作系统只能调度到线程,如果多个任务在同一条线程上执行,而不是开启多个线程执行,能减轻操作系统的负担
在其他编译型语言中,由于多线程可以利用多核,协程的概念被弱化了,对于CPython,多线程也不能利用多核,所有协程的概念变得至关重要
特点:
不能利用多核
用户级的概念,操作系统不可见
协程不存在数据安全问题(不涉及操作系统的调度,不会出现数据不安全)
能够从程序级别感知到的IO操作:网络,时间
在网络操作情况选,协程的效率比线程高
在多个任务之间切换
生成器本身就是一个协程
async 使用yield关键字实现了规避IO的操作
tornado框架也是利用yield
即便是程序级别,yield切换也会带来一些时间开销
def consumer(): while True: # 必须生产者先生产 ,再由消费者进行消费 goods = yield print(goods) def producer(): c = consumer() #产生生成器 next(c) for i in range(10): c.send('土豆%s'%i) #传参数给goods producer()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import time from greenlet import greenlet def eat(): print('start eating') g2.switch() # 切换执行g2协程 time.sleep(1) print('eating finished') def sleep(): print('sleep eating') time.sleep(1) print('sleep finished') g1.switch() #切换执行g1协程 g1 = greenlet(eat) #创建协程对象 g2 = greenlet(sleep) #创建协程对象 g1.switch() #执行 结果: start eating sleep eating sleep finished eating finished
gevent
内部使用了greenlet的切换机制,实现了遇到IO自动进行切换
from threading import currentThread from gevent import monkey monkey.patch_all() import time import gevent def eat(): print('start eating',currentThread()) # 打印线程名 DummyThread-1 伪线程 time.sleep(1) print('eating finished') def sleep(): print('sleep eating',currentThread()) # 打印线程名 DummyThread-1 伪线程 time.sleep(1) print('sleep finished') # for i in range(5): # g1 = gevent.spawn(eat) # g2 = gevent.spawn(sleep) # g1.join() # 如果没有join,来不及运行函数内的代码 主代码就会运行结束 # g2.join() #join的另一种用法 g_l = [] for i in range(5): g1 = gevent.spawn(eat) g2 = gevent.spawn(sleep) g_l.append(g1) g_l.append(g2) gevent.joinall(g_l)
协程实现socketserver
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from gevent import monkey monkey.patch_all() import socket import gevent def talk(conn): while True: msg = conn.recv(1024).decode('utf-8') conn.send( msg.upper().encode('utf-8')) server = socket.socket() server.bind(('127.0.0.1',9004)) server.listen() while True: conn,addr = server.accept() g = gevent.spawn(talk,conn)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import threading def client(): client = socket.socket() client.connect(('127.0.0.1',9004)) while True: client.send(b'hello') msg = client.recv(1024) print(msg) for i in range(500): threading.Thread(target=client).start() # 实现500个线程并发
两个协程模块:
gevent 基于greenlet 使用更方便,性能相对低
asyncio 基于yield 性能更好
协程爬虫(gevent应用)
# gevent处理问题很简便,直接扔给spawn就行了 from gevent import monkey monkey.patch_all() import gevent import time from urllib import request def get_page(url_t): ret = request.urlopen(url_t[0]) content = ret.read() with open(url_t[1]+'_new','wb') as f: f.write(content) url_lst = [ ('http://www.sogou.com','sogou'), ('http://www.baidu.com','baidu'), ('http://www.douban.com','douban'), ('http://www.cnblogs.com','cnblogs1'), ('http://www.cnblogs.com/Eva-J','cnblogs2'), ('http://www.cnblogs.com/Eva-J/articles/8324673.html','cnblogs3'), ('http://www.cnblogs.com/Eva-J/p/7277026.html','cnblogs4'), ('http://www.JD.com','jd'), ('http://www.taobao.com','tb') ] start = time.time() g_l = [] for url_t in url_lst: g = gevent.spawn(get_page,url_t) g_l.append(g) gevent.joinall(g_l) print(time.time() - start)