python 协程
2020-12-13 16:10
阅读:388
标签:目的 table string 服务端 enc tom top 客户 pip3
协程
一、协程的本质:
单线程实现并发,在应用程序里控制多个任务的切换+保存状态
二、协程的目的:
- 想要在单线程下实现并发
- 并发指的是多个任务看起来是同时运行的
- 并发=切换+保存状态
三、补充:
- yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
- send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换
- 如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制)
四、优点
- 应用程序级别速度要远远高于操作系统的切换
五、缺点
- 多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地,该线程内的其他的任务都不能执行了
- 一旦引入协程,就需要检测单线程下所有的IO行为,实现遇到IO就切换,少一个都不行,因为如果一个任务阻塞了,整个线程就阻塞了,其他的任务即便是可以计算,但是也无法运行了
注意:单纯地切换反而会降低运行效率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#并发执行 import time
def producer():
g = consumer()
next (g)
for i in range ( 100 ):
g.send(i)
def consumer():
while True :
res = yield
start_time = time.time()
producer() stop_time = time.time()
print (stop_time - start_time)
#串行 import time
def producer():
res = []
for i in range ( 10000000 ):
res.append(i)
return res
def consumer(res):
pass
start_time = time.time()
res = producer()
consumer(res) stop_time = time.time()
print (stop_time - start_time)
|
greenlet
greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。
注意:单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#pip3 install greenlet from greenlet import greenlet
import time
def eat(name):
print ( ‘%s eat 1‘ % name)
time.sleep( 2 )
g2.switch( ‘tom‘ )
print ( ‘%s eat 2‘ % name)
g2.switch()
def play(name):
print ( ‘%s play 1‘ % name )
g1.switch()
print ( ‘%s play 2‘ % name )
g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch( ‘tom‘ )
""" tom eat 1 tom play 1 tom eat 2 tom play 2 """ |
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的返回值
二、补充:
- gevent.sleep(2)模拟的是gevent可以识别的io阻塞,
- 而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了
- from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
#pip3 install gevent from gevent import monkey;monkey.patch_all()
import gevent
import time
def eat(name):
print ( ‘%s eat 1‘ % name)
time.sleep( 3 )
print ( ‘%s eat 2‘ % name)
def play(name):
print ( ‘%s play 1‘ % name)
time.sleep( 2 )
print ( ‘%s play 2‘ % name)
start_time = time.time()
g1 = gevent.spawn(eat, ‘tom‘ )
g2 = gevent.spawn(play, ‘rose‘ )
g1.join() g2.join() stop_time = time.time()
print (stop_time - start_time)
""" tom eat 1 rose play 1 rose play 2 tom eat 2 3.003171920776367 """ from gevent import monkey;monkey.patch_all()
import gevent
import time
def eat(name):
print ( ‘%s eat 1‘ % name)
time.sleep( 3 )
print ( ‘%s eat 2‘ % name)
def play(name):
print ( ‘%s play 1‘ % name)
time.sleep( 2 )
print ( ‘%s play 2‘ % name)
g1 = gevent.spawn(eat, ‘tom‘ )
g2 = gevent.spawn(play, ‘rose‘ )
# g1.join() # g2.join() gevent.joinall([g1,g2]) |
三、通过gevent实现单线程下的socket并发
from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,否则gevent无法识别socket的阻塞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
""" 服务端 #基于gevent实现 """ from gevent import monkey,spawn;monkey.patch_all()
from socket import *
def communicate(conn):
while True :
try :
data = conn.recv( 1024 )
if not data: break
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
def server(ip,port):
server = socket(AF_INET, SOCK_STREAM)
server.bind((ip,port))
server.listen( 5 )
while True :
conn, addr = server.accept()
spawn(communicate,conn)
server.close()
if __name__ = = ‘__main__‘ :
g = spawn(server, ‘127.0.0.1‘ , 8090 )
g.join()
""" 客户端 """ from socket import *
from threading import Thread,currentThread
def client():
client = socket(AF_INET,SOCK_STREAM)
client.connect(( ‘127.0.0.1‘ , 8090 ))
while True :
client.send(( ‘%s hello‘ % currentThread().getName()).encode( ‘utf-8‘ ))
data = client.recv( 1024 )
print (data.decode( ‘utf-8‘ ))
client.close()
if __name__ = = ‘__main__‘ :
for i in range ( 500 ):
t = Thread(target = client)
t.start()
|
python 协程
标签:目的 table string 服务端 enc tom top 客户 pip3
原文地址:https://www.cnblogs.com/kingforn/p/11618298.html
评论
亲,登录后才可以留言!