1. 程式人生 > 程式設計 >python使用協程實現併發操作的方法詳解

python使用協程實現併發操作的方法詳解

本文例項講述了python使用協程實現併發操作的方法。分享給大家供大家參考,具體如下:

協程

協程是一種使用者態的輕量級執行緒,又稱微執行緒。

協程擁有自己的暫存器上下文和棧,排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧。因此:協程能保留上一次呼叫時的狀態(即所有區域性狀態的一個特定組合),每次過程重入時,就相當於進入上一次呼叫的狀態,換種說法:進入上一次離開時所處邏輯流的位置。

優點:

  1. 無需執行緒上下文切換的開銷
  2. 無需原子操作鎖定及同步的開銷
  3. 方便切換控制流,簡化程式設計模型
  4. 高併發+高擴充套件性+低成本:一個CPU支援上萬的協程都不是問題。所以很適合用於高併發處理。

所謂原子操作是指不會被執行緒排程機制打斷的操作;這種操作一旦開始,就一直執行到結束,中間不會有任何 context switch (切換到另一個執行緒)。

原子操作可以是一個步驟,也可以是多個操作步驟,但是其順序是不可以被打亂,或者切割掉只執行部分。視作整體是原子性的核心。

缺點:

  1. 無法利用多核資源:協程的本質是個單執行緒,它不能同時將 單個CPU 的多個核用上,協程需要和程序配合才能執行在多CPU上.當然我們日常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  2. 進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程式

使用Gevent

gevent是python的一個併發框架,以微執行緒greenlet為核心,使用了epoll事件監聽機制以及諸多其他優化而變得高效.

  • 簡單示例

gevent的sleep可以交出控制權,當我們在受限於網路或IO的函式中使用gevent,這些函式會被協作式的排程, gevent的真正能力會得到發揮。Gevent處理了所有的細節, 來保證你的網路庫會在可能的時候,隱式交出greenlet上下文的執行權。

import gevent
def foo():
  print('running in foo')
  gevent.sleep(0)
  print('com back from bar in to foo')
def bar():
  print('running in bar')
  gevent.sleep(0)
  print('com back from foo in to bar')
# 建立執行緒並行執行程式
gevent.joinall([
  gevent.spawn(foo),gevent.spawn(bar),])

執行結果

running in foo
running in bar
com back from bar in to foo
com back from foo in to bar

  • 同步非同步
import random
import gevent
def task(pid):
  gevent.sleep(random.randint(0,2) * 0.001)
  print('Task %s done' % pid)
def synchronous():
  for i in range(1,10):
    task(i)
def asynchronous():
  threads = [gevent.spawn(task,i) for i in range(10)]
  gevent.joinall(threads)
print('Synchronous:')
synchronous()
print('Asynchronous:')
asynchronous()

執行輸出

Synchronous:
Task 1 done
Task 2 done
Task 3 done
Task 4 done
Task 5 done
Task 6 done
Task 7 done
Task 8 done
Task 9 done
Asynchronous:
Task 1 done
Task 4 done
Task 5 done
Task 9 done
Task 6 done
Task 0 done
Task 2 done
Task 3 done
Task 7 done
Task 8 done

  • 以子類的方法使用協程

可以子類化Greenlet類,過載它的_run方法,類似多執行緒和多程序模組

import gevent
from gevent import Greenlet
class Test(Greenlet):
  def __init__(self,message,n):
    Greenlet.__init__(self)
    self.message = message
    self.n = n
  def _run(self):
    print(self.message,'start')
    gevent.sleep(self.n)
    print(self.message,'end')
tests = [
  Test("hello",3),Test("world",2),]
for test in tests:
  test.start() # 啟動
for test in tests:
  test.join() # 等待執行結束

  • 使用monkey patch修改系統標準庫(自動切換協程)

當一個greenlet遇到IO操作時,比如訪問網路,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。

由於IO操作非常耗時,經常使程式處於等待狀態,有了gevent為我們自動切換協程,就保證總有greenlet在執行,而不是等待IO。

由於切換是在IO操作時自動完成,所以gevent需要修改Python自帶的一些標準庫,這一過程在啟動時通過monkey patch完成

import gevent
import requests
from gevent import monkey
monkey.patch_socket()
def task(url):
  r = requests.get(url)
  print('%s bytes received from %s' % (len(r.text),url))
gevent.joinall([
  gevent.spawn(task,'https://www.baidu.com/'),gevent.spawn(task,'https://www.qq.com/'),'https://www.jd.com/'),])

執行輸出

2443 bytes received from https://www.baidu.com/
108315 bytes received from https://www.jd.com/
231873 bytes received from https://www.qq.com/

可以看出3個網路操作是併發執行的,而且結束順序不同

參考連結:http://hhkbp2.github.io/gevent-tutorial/

更多關於Python相關內容感興趣的讀者可檢視本站專題:《Python程序與執行緒操作技巧總結》、《Python資料結構與演算法教程》、《Python函式使用技巧總結》、《Python字串操作技巧彙總》、《Python入門與進階經典教程》、《Python+MySQL資料庫程式設計入門教程》及《Python常見資料庫操作技巧彙總》

希望本文所述對大家Python程式設計有所幫助。