python-day37--协程

一、 协程介绍

单线程下实现并发,提升运行效率,

  1.自己控制切换,保存状态

  2.遇到I/O切         (单纯的CPU切没意义,只有在遇到I/O的时候切才有效率)

一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。、

需要强调的是:

#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
#2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

对比操作系统控制线程的切换,用户在单线程内控制协程的切换

优点如下:

#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
#2. 单线程内就可以实现并发的效果,最大限度地利用cpu

缺点如下:

#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
#2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

总结协程特点:

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
  4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

二、用yeild实现协程

 1 import time
 2 def init(func):
 3     def wrapper(*args,**kwargs):
 4         g=func(*args,**kwargs)
 5         next(g)
 6         return g
 7     return wrapper
 8 @init
 9 def consumer():
10     while True:
11         x=yield
12         print(x)
13 
14 def producer(target):
15     for i in range(10):
16         # time.sleep(1)
17         target.send(i)
18 
19 producer(consumer())
View Code

三、greenlet模块(了解就行,遇到I/O不能切)

 1 from greenlet import greenlet
 2 import time
 3 def eat(name):
 4     print('%s eat 1' %name)
 5     time.sleep(10)      #在io阻塞的时候 不能来回切,会一直在这睡着
 6     g2.switch('egon')
 7     print('%s eat 2' %name)
 8     g2.switch()
 9 def play(name):
10     print('%s play 1' %name)
11     g1.switch()
12     print('%s play 2' %name)
13 
14 g1=greenlet(eat)
15 g2=greenlet(play)
16 
17 #g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要
View Code

四、gevent模块

遇到IO阻塞时会自动切换任务

用法

#用法
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的返回值
 1 from gevent import monkey;monkey.patch_all()    #monkey;monkey.patch_all()  可以识别其他的I/O操作
 2 import gevent
 3 import time,threading
 4 def eat(name):
 5     print('%s eat 1' %name)
 6     time.sleep(2)
 7     print('%s eat 2' %name)
 8     return 'eat'
 9 
10 def play(name):
11     print('%s play 1' %name)
12     time.sleep(3)
13     print('%s play 2' %name)
14     return 'play'
15 
16 start=time.time()
17 g1=gevent.spawn(eat,'egon')
18 g2=gevent.spawn(play,'egon')
19 # g1.join()
20 # g2.join()
21 gevent.joinall([g1,g2])
22 print('',(time.time()-start))
23 print(g1.value)
24 print(g2.value)
25 
26 # 结果:
27 egon eat 1
28 egon play 1
29 egon eat 2
30 egon play 2
31 主 3.0018091201782227
32 eat
33 play

五、协程的应用

练习题1

 1 from gevent import monkey;monkey.patch_all()
 2 import gevent
 3 import requests
 4 import time
 5 
 6 def get_page(url):
 7     print('GET: %s' %url)
 8     response=requests.get(url)
 9     if response.status_code == 200:
10         print('%d bytes received from %s' %(len(response.text),url))
11 
12 start_time=time.time()
13 
14 # get_page('https://www.python.org/')   #串行
15 # get_page('https://www.yahoo.com/')    #串行
16 # get_page('https://github.com/')   #串行
17     
18 g1=gevent.spawn(get_page, 'https://www.python.org/')
19 g2=gevent.spawn(get_page, 'https://www.yahoo.com/')
20 g3=gevent.spawn(get_page, 'https://github.com/')
21 
22 gevent.joinall([g1,g2,g3])
23 stop_time=time.time()
24 print('run time is %s' %(stop_time-start_time))
爬虫练习

练习题2

 1 from gevent import monkey;monkey.patch_all()
 2 import gevent
 3 from socket import *
 4 def talk(conn,addr):
 5     while True:
 6         data=conn.recv(1024)
 7         print('%s:%s %s' %(addr[0],addr[1],data))
 8         conn.send(data.upper())
 9     conn.close()
10 
11 def server(ip,port):
12     s = socket(AF_INET, SOCK_STREAM)
13     s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
14     s.bind((ip,port))
15     s.listen(5)
16     while True:
17         conn,addr=s.accept()
18         gevent.spawn(talk,conn,addr)
19     s.close()
20 
21 if __name__ == '__main__':
22     server('127.0.0.1', 8088)
并发通信 服务端
 1 from multiprocessing import Process
 2 from socket import *
 3 def client(server_ip,server_port):
 4     client=socket(AF_INET,SOCK_STREAM)
 5     client.connect((server_ip,server_port))
 6     while True:
 7         cmd=input('')
 8         client.send(cmd.encode('utf-8'))
 9         msg=client.recv(1024)
10         print(msg.decode('utf-8'))
11 
12 if __name__ == '__main__':
13     client('127.0.0.1', 8088)
并发通信 客户端
原文地址:https://www.cnblogs.com/liuwei0824/p/7460168.html