Python并发编程基础 №⑦ 协程、gevent模块
协程
1.引入
进程 启动多个进程
进程之间是由操作系统负责调用线程 启动多个线程
真正被CPU执行的最小单位实际是线程
开启一个线程
创建一个线程 寄存器 堆栈
关闭一个线程
2.简介
协程本质上是一个线程能够在多个任务之间切换来节省一些IO时间协程中任务之间的切换也消耗时间,
但是开销要远远小于进程线程之间的切换
3.用生成器的例子,来描述协程原理
1 def consume(): 2 """ 3 特殊生成器,不是返回值,而是要传递值给他 4 :return: None 5 """ 6 while True: 7 n = yield 8 print('消费了数据:', n) 9 10 11 def produce(): 12 c = consume() 13 next(c) # 无此句报错:TypeError: can't send non-None value to a just-started generator 14 for i in range(10): 15 print('生成了数据:',i) 16 c.send(i) 17 18 19 produce()
4.真正的协程模块就是使用greenlet完成的切换
1 from greenlet import greenlet 2 3 4 def sing(): 5 print('...begin to sing... ') 6 g2.switch() 7 print('...end to sing... ') 8 g2.switch() 9 10 11 def dance(): 12 print('---start to dance ---') 13 g1.switch() 14 print('---stop dancing ---') 15 16 17 g1 = greenlet(sing) 18 g2 = greenlet(dance) 19 20 g1.switch()
5.协程模块 gevent 要用pop导入:windows下:cmd输入 pop3 install gevent 建议同时开启pycharm,否则可能安装失败!
1 from gevent import monkey 2 monkey.patch_all() 3 import gevent 4 import time 5 import threading 6 7 def sing(): 8 print('...begin to sing... ', threading.current_thread().getName()) # DummyThread-1 傀儡线程-1 (假的线程) 9 time.sleep(1) 10 print('...end to sing... ') 11 12 13 def dance(): 14 print('---start to dance ---', threading.current_thread()) # <_DummyThread(DummyThread-2, started daemon 1390457303880)> 15 time.sleep(1) 16 print('---stop dancing ---', threading.get_ident()) # 1390457303880 17 18 g1 = gevent.spawn(sing) 19 g2 = gevent.spawn(dance) 20 gevent.joinall((g1, g2)) # 没monkey.patch_all(),就识别不了阻塞(time的),就无法切换协程
进程和线程的任务切换,由操作系统完成
协程任务之间的切换由程序(代码)完成,只有遇到协程模块能识别的IO操作的时候,程序才会进行任务切换,实现并发的效果。
6.同步 和 异步
1 from gevent import monkey;monkey.patch_all() 2 import time 3 import gevent 4 5 6 def task(n): 7 time.sleep(1) 8 print(n) 9 10 11 def sync(): 12 ''' 13 同步生成:隔一秒打印一个数字 14 :return: 15 ''' 16 for i in range(10): 17 task(i) 18 19 20 def async(): 21 """ 22 异步生成:所有数字一起打印出来 23 :return: 24 """ 25 g_lst = [] 26 for i in range(10): 27 g = gevent.spawn(task, i) 28 g_lst.append(g) 29 gevent.joinall(g_lst) 30 31 # sync() 32 async()
7.小结:
协程 : 能够在一个线程中实现并发效果的概念
能够规避一些任务中的IO操作
在任务的执行过程中,检测到IO就切换到其他任务
多线程 被弱化了
协程 在一个线程上 提高CPU 的利用率
协程相比于多线程的优势 切换的效率更快
8.协程应用一,网络爬虫升级版
1 from gevent import monkey 2 monkey.patch_all() # ;作用是将多行显示在一行 ,monkey的作用是让gevent识别下面引入包中的io事件 3 import gevent 4 from urllib.request import urlopen 5 6 7 # 启动5个协程并发去抓取5个网站首页的内容,并打印其长度! 8 # 作用:在等待io的时间,去完成抓取其他网站首页的内容的任务,提高效率,节约时间成本! 9 10 11 def get(url): 12 """ 13 获取url地址对应网页的内容 14 :param url: 地址 15 :return: None 16 """ 17 response = urlopen(url) 18 content = response.read().decode('utf-8') 19 print(url, len(content)) 20 21 22 url_list = ( 23 'http://www.sohu.com', 'http://www.baidu.com', 'http://www.cnblogs.com', 24 'http://sports.sina.com.cn', 'http://jd.com') 25 26 g_list = [] 27 for url in url_list: 28 g = gevent.spawn(get, url) 29 g_list.append(g) 30 31 gevent.joinall(g_list)
9.协程应用二,socket聊天
1 # 服务器端 2 from gevent import monkey;monkey.patch_all() 3 import gevent 4 import socket 5 6 def talk(conn): 7 msg = conn.recv(1024).decode('utf-8') 8 print(msg) 9 ipt = input('>>>').encode('utf-8') 10 conn.send(ipt) 11 conn.close() 12 13 sk = socket.socket() 14 sk.bind(('127.0.0.1', 8888)) 15 sk.listen() 16 17 try: 18 while 1: 19 conn, addr = sk.accept() 20 # talk(conn) 21 gevent.spawn(talk, conn) 22 except: 23 sk.close() 24 25 26 #客户端 27 import socket 28 29 sk = socket.socket() 30 sk.connect(('127.0.0.1', 8888)) 31 32 sk.send(b'hello') 33 msg = sk.recv(1024).decode('utf-8') 34 print(msg)