Preface:
- 不管是線程(threading)或是進程(multiprocessing)都為一種搶占資源(競爭式)的型式執行程序
- 資源消耗大,且CPU切換耗時
Introduction:
Notice:
- 可利用多進程+協程實現CPU多核利用
- 要是協程中遇到阻塞的情況,因為單線程的原因會造成程序全阻塞
Usage:
使用生成器的yield實現協程操作:
傳統生產者消費者模型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def buyer(name):
print(name, "來去準備購物囉!!!")
while True:
buysomething = yield "老闆!我要買這個"
print ("買了一件東西 - %s" % buysomething)
def seller(thing):
buyer_response = next(shopping_person)
print(buyer_response)
shopping_person.send(thing)
print("謝謝光臨!商品重新上架 - %s"%thing)
if __name__ == "__main__":
shopping_list = {"小華":"糖果", "小明":"餅乾", "小馬":"果汁"}
for name,item in shopping_list.items():
shopping_person = buyer(name) # 創建生成器對象
sellsomething = seller( item )
其結果如下1
2
3
4
5
6
7
8
9
10
11
12小華 來去準備購物囉!!!
老闆!我要買這個
買了一件東西 - 糖果
謝謝光臨!商品重新上架 - 糖果
小明 來去準備購物囉!!!
老闆!我要買這個
買了一件東西 - 餅乾
謝謝光臨!商品重新上架 - 餅乾
小馬 來去準備購物囉!!!
老闆!我要買這個
買了一件東西 - 果汁
謝謝光臨!商品重新上架 - 果汁
yield可以記住函數執行到的位置
再次調用next()或是send()就可以回到函數內部繼續往下執行
greenlet (python第3方支持協程模塊)
須先安裝1
pip install gevent
一般執行函數的情況1
2
3
4
5
6
7
8
9
10
11def function_1():
print("我是函數_1顯示的第1行")
print("我是函數_1顯示的第2行")
def function_2():
print("我是函數_2顯示的第1行")
print("我是函數_2顯示的第2行")
if __name__ == "__main__":
function_1()
function_2()
結果如下所示1
2
3
4我是函數_1顯示的第1行
我是函數_1顯示的第2行
我是函數_2顯示的第1行
我是函數_2顯示的第2行
使用greenlet進行函式間的切換1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17from greenlet import greenlet
def function_1():
print("我是函數_1顯示的第1行")
greenlet_test2.switch()
print("我是函數_1顯示的第2行")
greenlet_test2.switch()
def function_2():
print("我是函數_2顯示的第1行")
greenlet_test1.switch()
print("我是函數_2顯示的第2行")
if __name__ == "__main__":
greenlet_test1 = greenlet(function_1)
greenlet_test2 = greenlet(function_2)
greenlet_test1.switch()
其結果如下1
2
3
4我是函數_1顯示的第1行
我是函數_2顯示的第1行
我是函數_1顯示的第2行
我是函數_2顯示的第2行
由上述我們可以知道greenlet中的switch方法類似生成器中的next()或是send()方法
可來回的在函數之間進行執行順序的切換
- 協程中的切換是指執行順序的切換而非CPU的切換
Gevent (python第3方支持協程模塊)
透過gevent實現程序併發效果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import gevent
def fun_1():
print("開始執行fun_1")
gevent.sleep()
print("仍在執行fun_1")
gevent.sleep(1)
print("結束fun_1執行")
def fun_2():
print("開始執行fun_2")
gevent.sleep()
print("仍在執行fun_2")
gevent.sleep(2)
print("結束fun_2執行")
if __name__ == "__main__":
g1 = gevent.spawn(fun_1) #創造一個fun_1的gevent物件並啟用此物件
g2 = gevent.spawn(fun_2)
g1.join()
g2.join()
其結果為1
2
3
4
5
6開始執行fun_1
開始執行fun_2
仍在執行fun_1
仍在執行fun_2
結束fun_1執行
結束fun_2執行
gevent.sleep()就是用做模擬IO阻塞的情況,亦是交出自己的線程控制權
在其他函式面臨阻塞時進行切換到其他函式繼續執行藉此提高整個程序的執行效率
應用於requests庫
可以看一下如果使用一般的方式進行網頁抓取需要多少時間1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import requests
import time
def GET_url(url):
status = requests.get(url)
data = status.text
print("GET %s status:%s & length:%d"%(url,status,len(data)))
if __name__ == "__main__":
start = time.time()
url_list = ['http://www.taobao.com',
"http://shopee.tw/",
"https://www.autobuy.tw/",
'https://github.com',
'https://tw.yahoo.com',
'http://www.python.org']
for url in url_list:
GET_url(url)
print("spent time: %d sec"%(time.time()-start))
結果如下1
2
3
4
5
6
7GET http://www.taobao.com status:<Response [200]> & length:237961
GET http://shopee.tw/ status:<Response [200]> & length:78523
GET https://www.autobuy.tw/ status:<Response [200]> & length:107359
GET https://github.com status:<Response [200]> & length:52086
GET https://tw.yahoo.com status:<Response [200]> & length:443617
GET http://www.python.org status:<Response [200]> & length:48940
spent time: 14 sec
若使用gevent進行切換1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import requests
import gevent
import time
def GET_url(url):
status = requests.get(url)
data = status.text
print("GET %s status:%s & length:%d"%(url,status,len(data)))
if __name__ == "__main__":
start = time.time()
gevent.joinall([
gevent.spawn(GET_url,'http://www.taobao.com'),
gevent.spawn(GET_url,"https://shopee.tw/"),
gevent.spawn(GET_url,"https://www.autobuy.tw/"),
gevent.spawn(GET_url, 'https://github.com'),
gevent.spawn(GET_url, 'https://tw.yahoo.com'),
gevent.spawn(GET_url, 'http://www.python.org'),
])
print("spent time: %d sec"%(time.time()-start))
- spawn的第一個參數為函數名,第二個參數為函數所需的參數
結果如下1
2
3
4
5
6
7GET http://www.taobao.com status:<Response [200]> & length:237961
GET https://shopee.tw/ status:<Response [200]> & length:78523
GET https://www.autobuy.tw/ status:<Response [200]> & length:106504
GET https://github.com status:<Response [200]> & length:52084
GET https://tw.yahoo.com status:<Response [200]> & length:447471
GET http://www.python.org status:<Response [200]> & length:48940
spent time: 4 sec
在windows作業系統上可添加以下代碼使效果更為顯著1
2from gevent import monkey
monkey.patch_all()
Reference:
https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001407503089986d175822da68d4d6685fbe849a0e0ca35000
http://www.cnblogs.com/alex3714/articles/5248247.html