eventlet 之 monkeypatch 帶來的若干兼容性問題實例分析

概述

最近須要在一個基於nameko/eventlet的服務中集成grpc client, 遇到了一個monkeypatch帶來的兼容性問題, 測試代碼以下:python

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才中斷, 並在主線程繼續運行.this

2. 不對thread進行patch

在TestThread進入start以後, self.__started.wait()直接返回, 值得注意的是, 在start內部調用_start_new_thread就直接啓動子線程, 而且直接返回了!
clipboard.pngspa

結論

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

相關文章
相關標籤/搜索