標籤:

為什麼gevent不能配合非純Python的程序一起使用?

在pymemcache的github主頁上,有一段對pylibmc的描述"It also isnt pure Python, so using it with libraries like gevent is out of the question.",我一直想不通為什麼,求答疑解惑。

--------------------------分割線---------------------------

多謝各位,這個問題算是弄清楚了,但還有一點不解的是,既然協程同一時刻只有一個在運行,那為什麼兩個協程不能訪問同一個socket呢?為什麼有的材料上說會導致未定義的行為?


額,我應該不是這樣跟 @劉鑫 老師說的。

gevent 底層是 libev,它從其之上再造了一個 socket 模塊,具有跟標準庫 socket 兼容的介面。

gevent.monkey.patch_all() 會用它的 socket 替代掉標準庫里的 socket,所以當你的代碼 import socket,等價於 import gevent.socket;time/thread/threading/queue/... 等模塊類似。

當一個庫的代碼是純 Python 的時候,加上 monkey patch 技法,那麼這個庫使用的就是 gevent.socket 了,從而不需要任何更改就能夠獲得 gevent 的同步代碼非同步執行的「超級牛力」。

pylibmc 這個庫我不了解,但從題主的描述上來,應該是它的 I/O 是直接用 C 語言調用系統的 socket API 簇來做的,gevent 沒有辦法替換它,所以不能兼容。

最後,「gevent不能配合非純Python的程序一起使用」這種情況是不純在的,如果你寫一個C擴展模塊,裡面沒有I/O,比如計算 100! 什麼的,那跟 gevent 一起使用無妨。


這個我斗膽回答一下,畢竟山寨過gevent。 @賴勇浩 的解釋基本認同,但說gevent再造了socket模塊這點是不準確的。gevent使用的也是python原生的socket介面,只是在上面做了層封裝,利於實現其基於libev的調度。這裡的一個要點是,使用gevent創建出來的socket都被設置了setblocking(false),所以讀寫都會立即返回,如果返回的是幾個特定錯誤碼,比如EAGAIN, gevent就會把當前協程掛起,等到libev說這個socket又能讀寫了,再回調把協程喚醒。如果你自己用c寫一套socket庫,介面約定和python原生的一致,也可以用gevent monkey起來用。

為什麼兩個協程不能同時用一個socket呢?想下上面的場景,如果兩個協程一起讀,都掛起了,等到能讀的時候喚醒,這個時候就涉及到先喚醒誰的問題,如果是流式socket不同的讀寫順序會導致讀出來的內容跟預期不一樣。如果是包式socket,其實一起讀是沒問題的。不過印象中gevent對掛起在同一個句柄上的同一種事件是沒做合理排序的,所以不能同時多個協程讀或者寫。一個讀一個寫是沒有問題的。

對於socket,如果要真的實現並發讀,也是可能的,只是api會比較奇怪。至於並發寫,golang就是實現了的,通過在socket上加一把鎖,應用層的感受是多個goroutine一起write完全沒有問題。


我記得聽大神 @賴勇浩 講過,似乎是 gevent 切換 context 的時候,純 Python 程序會由虛擬機正確的掛起和恢復, C 模塊就不一定了。


樓主可以看下這個回答,解釋的很清楚。

關於 python gevent 架框 作為 TCP伺服器 的 代碼問題 , 每個 socket 的 消息 接收 是否有使用 事件監聽回調的方法呢? - 楊俊偉的回答


可能是因為pymemcache的代碼用了阻塞io(好多遺留的c庫都有這個問題比如mysql那個庫也有),協程執行到pymc從memcached里讀數據的時候,是會被阻塞住,無法切換到別的協程。


推薦閱讀:

Python並發學習筆記:從協程到GEVENT(二)
gunicorn和uwsgi是怎麼在使用gevent的,gunicorn/uwsgi和gevent?
gevent、eventlet、Twisted、Tornado各有什麼區別和優劣?

TAG:gevent |