[問題] 停止正在執行 3rd party lib 的 thread

看板Python作者 (Cary Wu)時間9年前 (2014/08/22 10:03), 9年前編輯推噓6(6039)
留言45則, 1人參與, 最新討論串1/1
程式執行登入時需要在另一個裝置做一些認證 在認證完成前原本程式的 UI 可以取消登入 但是此時做認證的 thread block 在一個 3rd party lib 等待認證回應的 function 所以不能用一般在 thread 下 check event 的方式來決定要不要離開 Google 了一下,找到利用 thread-id raise exception 的方式 http://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread-in-python shorten: http://goo.gl/MRRDFA 但是怎麼試也無法成功...都是 invalid thread ID 嘗試用文中的 walk 找 tid,或是用 threading.current_thread().ident 得到的都是一個很長的數字,也許不是真正的 tid? 所以試着用 system call 的方式 libc = ctypes.cdll.LoadLibrary('libc.so.6') tid = libc.syscall(186) 的方式來得到 tid 但是丟進去 PyThreadState_SetAsyncExc 裏還是 invalid tid... 也就是 return 是 0 請問該如何得到正確的 tid? 或是有其他方式可以 kill thread? (最好是 portable 的) 也順便問問是否有其他更好的做法可以避免需要 kill thread? -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 1.34.244.41 ※ 文章網址: http://www.ptt.cc/bbs/Python/M.1408672998.A.6E2.html ※ 編輯: carylorrk (1.34.244.41), 08/22/2014 10:20:57

08/22 20:45, , 1F
我想先說明,終止線程跟 OS 本身有極高的相關性
08/22 20:45, 1F

08/22 20:45, , 2F
我印象中目前並無某種方法,或某個內建的 API
08/22 20:45, 2F

08/22 20:46, , 3F
可以保證的在所有的系統上,終止某個特定線程。
08/22 20:46, 3F

08/22 20:46, , 4F
至少 Win32 得用 TerminateThread,不同於 pthread
08/22 20:46, 4F

08/22 20:46, , 5F
我先假設你所使用的作業系統所操作的是 pthread
08/22 20:46, 5F

08/22 20:46, , 6F
無論是透過 get_ident 或用 ident 都是拿到 pthread_t
08/22 20:46, 6F

08/22 20:46, , 7F
也就是 pthread_self() 會拿到的內容 (可查源碼參照)
08/22 20:46, 7F

08/22 20:46, , 8F
你拿到的數值會有點大,我猜你的系統應該是 64 bit
08/22 20:46, 8F

08/22 20:47, , 9F
StackOverflow 那則被打勾的回應是常見的好方法
08/22 20:47, 9F

08/22 20:47, , 10F
至少這個方式不是真正強制硬砍掉 Thread 運行
08/22 20:47, 10F

08/22 20:47, , 11F
然而之所以你會拿到 invalid thread ID 的原因是
08/22 20:47, 11F

08/22 20:47, , 12F
你得改所有用到 PyThreadState_SetAsyncExc 的部分
08/22 20:47, 12F

08/22 20:47, , 13F
foobar = ctypes.pythonapi.PyThreadState_SetAsyncExc
08/22 20:47, 13F

08/22 20:47, , 14F
foobar.argtypes = [ctypes.c_long, ctypes.py_object]
08/22 20:47, 14F

08/22 20:48, , 15F
res = foobar(tid, ctypes.py_object(exctype))
08/22 20:48, 15F

08/22 20:48, , 16F
請把原文章中的 _async_raise 前幾行,適度修改如上
08/22 20:48, 16F

08/22 20:49, , 17F
如此一來就應該不會出現 invalid thread ID 的狀況
08/22 20:49, 17F

08/22 20:50, , 18F
原因是在 64 bit 的環境中,得寫清楚帶進去的資料型態
08/22 20:50, 18F

08/22 20:51, , 19F
再透過 _async_raise(thr.ident, RuntimeError) 來終止
08/22 20:51, 19F

08/22 20:53, , 20F
至於是否有更好的做法? 可以開 child process 也是一種
08/22 20:53, 20F

08/22 20:55, , 21F
可以把傷害擺到另外一個進程,儘管還是會有機率出狀況
08/22 20:55, 21F

08/22 20:55, , 22F
最好的方式是改 3rd party lib 使其 graceful exit :D
08/22 20:55, 22F

08/22 20:57, , 23F
行有餘力還可以順便 merge 回 master/trunk 之類的 XD
08/22 20:57, 23F
感謝你的回覆,後來發現裏面推文就有了 忘記來這邊改XD 果然發文之前要好好爬文啊~ 擺到另外一個 process 的方式下面推文也有,不過感覺其實是差不多的~ 至於改 code... trace 了一下裏面有使用 requests 這個 lib 用 session.get() 經過 verification 抓 json 會造成 block 比較好的方式是改用 async 的方法再去 busy wait 要中斷就 set flag 結束 while loop 這樣 XD? 可惜不能 merge 回去,因爲這段 code 最近因爲被某 L 社要求撤掉了 Orz ※ 編輯: carylorrk (1.34.244.41), 08/23/2014 00:07:49

08/23 00:14, , 24F
其實我也沒仔細看所有回應,只是要說 ctypes 在 64bit
08/23 00:14, 24F

08/23 00:15, , 25F
的環境得多設定 argtypes 或者直接類似該討論串中某篇
08/23 00:15, 25F

08/23 00:15, , 26F
直接生出 c_long(tid) 再進行帶入的動作...
08/23 00:15, 26F

08/23 00:15, , 27F
最好的方式就是用 event / mutex 實作 graceful exit
08/23 00:15, 27F

08/23 00:16, , 28F
或者用一個 atomic variable 來當作 stop flag 之類的
08/23 00:16, 28F

08/23 00:18, , 29F
畢竟沒看到 3rd party lib 的原始碼,我只能猜到這 XD
08/23 00:18, 29F

08/23 00:21, , 30F
至於 Python 還有個 GIL,如果用一些強制力砍掉 thread
08/23 00:21, 30F

08/23 00:21, , 31F
如果剛好被砍掉的 thread 是持有 GIL 的話,那就有趣了
08/23 00:21, 31F

08/23 00:23, , 32F
如果是 mission critical 的環境,就得考量更多的細節
08/23 00:23, 32F

08/23 00:24, , 33F
總之恭喜你有把問題解決囉 :D
08/23 00:24, 33F

08/23 00:27, , 34F
畢竟提及 child process 只是想避掉 GIL 這個老問題
08/23 00:27, 34F
瞭解~ 不過如果用 async raise 還會有 GIL 的問題嗎? 畢竟不是 kill thread,應該會 release GIL 吧~ ※ 編輯: carylorrk (1.34.244.41), 08/23/2014 01:16:53

08/23 01:35, , 35F
不應該有GIL的問題,誠如我一開始提到這不是強制砍掉它
08/23 01:35, 35F

08/23 01:37, , 36F
所謂的強制力是指 pthread_kill or TerminateThread
08/23 01:37, 36F

08/23 01:38, , 37F
然而如果能實作 graceful exit 還是最好的方向就是 :)
08/23 01:38, 37F

08/23 01:58, , 38F
因為 PyThreadState_SetAsyncExc 是請求觸發 exception
08/23 01:58, 38F

08/23 01:59, , 39F
並不會真的立刻把該 thread 給終止掉,聽起來雖然不錯
08/23 01:59, 39F

08/23 02:01, , 40F
然而如果該 thread 不幸卡在一個很久的 blocking I/O
08/23 02:01, 40F

08/23 02:02, , 41F
或者該 thread 剛好操作某個會卡很久的 C-API
08/23 02:02, 41F

08/23 02:03, , 42F
就不一定能如同原本預期,順利的把 thread 給終止掉了
08/23 02:03, 42F

08/23 02:05, , 43F
畢竟這個的原理是在 PyEval_EvalFrameEx 還沒跑 opcode
08/23 02:05, 43F

08/23 02:05, , 44F
之前進行檢查 tstate->async_exc 的值是否有被設定 :D
08/23 02:05, 44F

08/23 02:07, , 45F
可參考源碼瞭解更多細節http://goo.gl/878eMi 大概如此
08/23 02:07, 45F
原來如此!如果沒有解說我還真把它當神奇的黑魔法 XDD 看來還需要研究一下細節,說不定哪天手上沒有 source code 就用的到了XD 感謝~ ※ 編輯: carylorrk (1.34.244.41), 08/23/2014 03:10:49
文章代碼(AID): #1JzgJcRY (Python)