與子例程一樣,協(xié)程也是一種程序組件。 相對子例程而言,協(xié)程更為一般和靈活,但在實踐中使用沒有子例程那樣廣泛。 協(xié)程源自Simula和Modula-2語言,但也有其他語言支持。 協(xié)程更適合于用來實現(xiàn)彼此熟悉的程序組件,如合作式多任務(wù),迭代器,無限列表和管道。
協(xié)程擁有自己的寄存器上下文和棧,協(xié)程調(diào)度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復(fù)先前保存的寄存器上下文和棧。因此:協(xié)程能保留上一次調(diào)用時的狀態(tài)(即所有局部狀態(tài)的一個特定組合),每次過程重入時,就相當(dāng)于進入上一次調(diào)用的狀態(tài),換種說法:進入上一次離開時所處邏輯流的位置。
協(xié)程的優(yōu)缺點:
優(yōu)點
無需線程上下文切換的開銷
無需原子操作鎖定及同步的開銷(更改一個變量)
方便切換控制流,簡化編程模型
高并發(fā)+高擴展性+低成本:一個CPU支持上萬的協(xié)程都不是問題。所以很適合用于高并發(fā)處理。
缺點:
無法利用多核資源:協(xié)程的本質(zhì)是個單線程,它不能多核,協(xié)程需要和進程配合才能運行在多CPU上,當(dāng)然我們?nèi)粘K帉懙慕^大部分應(yīng)用都沒有這個必要,除非是CPU密集型應(yīng)用。
進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序
yield
def consumer(name):
print("--->starting eating baozi...")
while True:
new_baozi = yield # 直接返回
print("[%s] is eating baozi %s" % (name, new_baozi))
def producer():
r = con.__next__()
r = con2.__next__()
n = 0
while n < 5:
n += 1
con.send(n) # 喚醒生成器的同時傳入一個參數(shù)
con2.send(n)
print("\033[32;1m[producer]\033[0m is making baozi %s" % n)
if __name__ == '__main__':
con = consumer("c1")
con2 = consumer("c2")
p = producer()
Greenlet
安裝greenlet
pip3 install greenlet
# -*- coding:utf-8 -*-
from greenlet import greenlet
def func1():
print(12)
gr2.switch()
print(34)
gr2.switch()
def func2():
print(56)
gr1.switch()
print(78)
# 創(chuàng)建兩個攜程
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch() # 手動切換
Gevent
Gevent可以實現(xiàn)并發(fā)同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協(xié)程,Greenlet全部運行在主程序操作系統(tǒng)進程的內(nèi)部,但它們被協(xié)作式地調(diào)度。
安裝Gevent
pip3 install gevent
import gevent
def foo():
print('Running in foo')
gevent.sleep(2)
print('Explicit context switch to foo again')
def bar():
print('Explicit context to bar')
gevent.sleep(3)
print('Implicit context switch back to bar')
# 自動切換
gevent.joinall([
gevent.spawn(foo), # 啟動一個協(xié)程
gevent.spawn(bar),
])
頁面抓取
from urllib import request
from gevent import monkey
import gevent
import time
monkey.patch_all() # 當(dāng)前程序中只要設(shè)置到IO操作的都做上標(biāo)記
def wget(url):
print('GET: %s' % url)
resp = request.urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
urls = [
'https://www.python.org/',
'https://www.python.org/',
'https://github.com/',
'https://yw666.blog.51cto.com/',
]
# 串行抓取
start_time = time.time()
for n in urls:
wget(n)
print("串行抓取使用時間:", time.time() - start_time)
# 并行抓取
ctrip_time = time.time()
gevent.joinall([
gevent.spawn(wget, 'https://www.python.org/'),
gevent.spawn(wget, 'https://www.python.org/'),
gevent.spawn(wget, 'https://github.com/'),
gevent.spawn(wget, 'https://yw666.blog.51cto.com/'),
])
print("并行抓取使用時間:", time.time() - ctrip_time)
輸出
C:\Python\Python35\python.exe E:/MyCodeProjects/協(xié)程/s4.py
GET: https://www.python.org/
47424 bytes received from https://www.python.org/.
GET: https://www.python.org/
47424 bytes received from https://www.python.org/.
GET: https://github.com/
25735 bytes received from https://github.com/.
GET: https://blog.ansheng.me/
82693 bytes received from https://yw666.blog.51cto.com/.
串行抓取使用時間: 15.143015384674072
GET: https://www.python.org/
GET: https://www.python.org/
GET: https://github.com/
GET: https://blog.ansheng.me/
25736 bytes received from https://github.com/.
47424 bytes received from https://www.python.org/.
82693 bytes received from https://yw666.blog.51cto.com/.
47424 bytes received from https://www.python.org/.
并行抓取使用時間: 3.781306266784668
Process finished with exit code 0
本文出自 “一盞燭光” 博客,謝絕轉(zhuǎn)載!
更多建議: