eventlet 之 monkeypatch 帶來的若干相容性問題例項分析

waltr發表於2019-02-16

概述

最近需要在一個基於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

clipboard.png
程式在switch之後切換到TestThread執行, 似乎就切換不回到主執行緒了!按下ctrl+c後TestThread才中斷, 並在主執行緒繼續執行.

2. 不對thread進行patch

在TestThread進入start之後, self.__started.wait()直接返回, 值得注意的是, 在start內部呼叫_start_new_thread就直接啟動子執行緒, 並且直接返回了!
clipboard.png

結論

可見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的底層併發模式時, 請三思.

相關文章