協程
1、協程的本質:
單線程實現併發,在應用程序裏控制多個任務的切換+保存狀態python
2、協程的目的:
- 想要在單線程下實現併發
- 併發指的是多個任務看起來是同時運行的
- 併發=切換+保存狀態
3、補充:
- yiled能夠保存狀態,yield的狀態保存與操做系統的保存線程狀態很像,可是yield是代碼級別控制的,更輕量級
- send能夠把一個函數的結果傳給另一個函數,以此實現單線程內程序之間的切換
- 如何實現檢測IO,yield、greenlet都沒法實現,就用到了gevent模塊(select機制)
4、優勢
- 應用程序級別速度要遠遠高於操做系統的切換
5、缺點
- 多個任務一旦有一個阻塞沒有切,整個線程都阻塞在原地,該線程內的其餘的任務都不能執行了
- 一旦引入協程,就須要檢測單線程下全部的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自動切換來提高效率的問題。web
注意:單純的切換(在沒有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阻塞時會自動切換任務redis
1、用法:
- 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的返回值
2、補充:
- 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])
|
3、經過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()
|