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()
View Code
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的),就无法切换协程
View Code
进程和线程的任务切换,由操作系统完成
协程任务之间的切换由程序(代码)完成,只有遇到协程模块能识别的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()
View Code

 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)
View Code

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)
View Code
posted @ 2020-01-02 20:02  四方游览  阅读(189)  评论(0编辑  收藏  举报