概述
最近需要在一個基於nameko/eventlet的服務中整合grpc client, 遇到了一個monkeypatch帶來的相容性問題, 測試程式碼如下:
import eventlet
eventlet.monkey_patch(thread=True)
import threading
from grpc._cython import cygrpc
class TestThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
completion_queue = cygrpc.CompletionQueue()
while True:
_ = completion_queue.poll()
threading._VERBOSE = True
t = TestThread()
t.start()
print('if thread is not patched, this message will be printed')
當對thread模組patch之後, 程式卡在了t.start(), 只有按ctrl+c中斷該執行緒之後, 程式才繼續執行. 但如果不對thread進行patch, 執行緒start之後, 程式繼續執行. 這是為什麼呢?
分析
使用pdb進行除錯, 分為兩種情況:
1. 對thread進行patch
程式在switch之後切換到TestThread執行, 似乎就切換不回到主執行緒了!按下ctrl+c後TestThread才中斷, 並在主執行緒繼續執行.
2. 不對thread進行patch
在TestThread進入start之後, self.__started.wait()直接返回, 值得注意的是, 在start內部呼叫_start_new_thread就直接啟動子執行緒, 並且直接返回了!
結論
可見monkeypatch修改了threading標準庫中的_start_new_thread方法, Condition類等. 當patch之後,_start_new_thread方法並不直接啟動執行緒, 而是返回一個greenlet, 在這個問題當中, grpc呼叫的是一個c extension中的threading pool, monkeypatch無法對這個extension進行patch, 導致了後來switch到這個greenlet中時其實是進入到另一個執行緒中. 因為greenlet無法在不同的執行緒中切換, 導致程式無法從TestThread切回來, 只有主動去中斷TestThread, 才能被恢復.
自從遇到了這個問題, 以後做專案的併發模型就更加慎重了:). 如果不清楚monkeypatch到底做了些什麼, 在選擇協程做python的底層併發模式時, 請三思.