首頁 後端開發 Python教學 不要強制殺掉python線程

不要強制殺掉python線程

Feb 28, 2017 am 09:06 AM

前言:

    不要試圖用強制方法殺死一個python線程,這從服務設計就存在不合理性。 多線程本用來任務的協作並發,如果你使用強製手段幹掉線程,那麼很大幾率出現意想不到的bug。  請記住一點,鎖定資源不會因為執行緒退出而釋放鎖定資源 !

我們可以舉出兩個常見的例子:

1. 有個A線程拿到了鎖,因為他是被強制幹掉的,沒能及時的release()釋放鎖資源,那麼導致所有的執行緒獲取資源是都被阻塞下去,這就是典型的死鎖場景。

2.在常見的生產消費者的場景下,消費者從任務隊列獲取任務,但是被幹掉後沒有把正在做的任務丟回隊列中,那麼這就造成了數據丟失。

下面是java和python終止執行緒的方法:

java有三種方法可以讓終止執行緒:

1. 使用退出標誌,使執行緒正常退出,也就是當run方法完成後執行緒終止。
2. 使用stop方法強行終止執行緒(不建議使用,因為stop和suspend、resume一樣,也可能發生不可預料的結果)。
3. 使用interrupt方法中斷執行緒。

python可以有兩種方法:

1. 退出標記
2. 使用ctypes強行殺掉執行緒

##不管是python還是java環境下,理想的停止退出線程方法是讓線程自個自殺,所謂的線程自殺就是你給他一個標誌位,他退出線程。

下面我們會採用多種方法來測試 停止python執行緒的異常情況。我們檢視一個行程所有的執行緒,  行程是用過掌控資源,執行緒是用作調度單元,行程要被調度執行必須要有一個執行緒,預設的執行緒和行程的pid一樣的。

ps -mp 31449 -o THREAD,tid
 
USER   %CPU PRI SCNT WCHAN USER SYSTEM  TID
root   0.0  -  - -     -   -   -
root   0.0 19  - poll_s  -   - 31449
root   0.0 19  - poll_s  -   - 31450
登入後複製

取得到了流程所有的執行緒後,透過strace得知31450 是需要我們kill的執行緒id,當我們kill的時候,會出現整個行程都崩潰的情況。 在多執行緒環境下,產生的訊號是傳遞給整個行程的,一般而言,所有執行緒都有機會收到這個訊號,行程在收到訊號的的執行緒上下文執行訊號處理函數,具體是哪個執行緒執行的難以獲知。也就是說,訊號會隨機發個該行程的一個執行緒。

strace -p <span style="font-size:14px;line-height:21px;">31450</span> Process <span style="font-size:14px;line-height:21px;">31450</span> attached - interrupt to quit
select(0, NULL, NULL, NULL, {0, 320326}) = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = ? ERESTARTNOHAND (To be restarted)
--- SIGTERM (Terminated) @ 0 (0) ---
Process <span style="font-size:14px;line-height:21px;">31450</span> detached
登入後複製

上面出現的問題其實跟pthread的說明是一致的。當我們在python程式碼裡加入signal 訊號處理函數後,回呼函數可以防止整個行程的退出,那麼問題來了,透過訊號函數不能辨識你要幹掉哪一個線程,也就是說,不能精準的幹掉某個線程。你雖然把訊號發給31450線程id,但是訊號受理人是所屬進程的任何一個,另外傳給訊號處理函數的參數只有訊號數和訊號stack而已,可有可無的。

加上了訊號處理後,不會退出進程


select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = ? ERESTARTNOHAND (To be restarted)
--- SIGTERM (Terminated) @ 0 (0) ---
rt_sigreturn(0xffffffff)        = -1 EINTR (Interrupted system call)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
登入後複製

如果想從外部通知殺掉某個線程,那麼可以建構使用rpc服務,或是別的方式通信,signal訊號不可以,因為無法無法傳遞更多的訊息。

python的線程不是模擬的,是真實的內核線程,內核呼叫pthread方法,但Python上層沒有提供關閉線程的方法,這就需要我們自己把握了。強烈建議使用 event 或 自訂標誌位元的方法, 如果非要強制殺掉線程,那麼可以用python ctypes PyThreadState SetAsyncExc 方法強制退出,這樣對於運行的python服務沒有什麼影響。


函數的實作原理比較簡單,其實也是在python虛擬機器裡做個標示位,然後由虛擬機器執行一個例外來取消線程,虛擬機會幫你做好try cache。 記得不要在外部殺掉python的某個線程,雖然你能透過ctypes找到線程id,但你直接kill會幹掉整個行程的。

下面的程式碼是用ctypes 殺掉執行緒的範例,不建議使用,因為太粗暴了.


##
import ctypes
 
def terminate_thread(thread):
  if not thread.isAlive():
    return
 
  exc = ctypes.py_object(SystemExit)
  res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
    ctypes.c_long(thread.ident), exc)
  if res == 0:
    raise ValueError("nonexistent thread id")
  elif res > 1:
    ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
    raise SystemError("PyThreadState_SetAsyncExc failed")
登入後複製

##咱們簡單look一下PyThreadState原始碼,總而言之觸發線程的異常模式。 有興趣的人可以閱讀 python pystate.c 的設計,配合著youtube的一些影片分享。


 int
PyThreadState_SetAsyncExc(long id, PyObject *exc) {
  PyInterpreterState *interp = GET_INTERP_STATE();
  ...
  HEAD_LOCK();
  for (p = interp->tstate_head; p != NULL; p = p->next) {
    if (p->thread_id == id) {
      从链表里找到线程的id,避免死锁,我们需要释放head_mutex。
      PyObject *old_exc = p->async_exc;
      Py_XINCREF(exc); #增加该对象的引用数
      p->async_exc = exc; # 更为exc模式
      HEAD_UNLOCK();
      Py_XDECREF(old_exc); # 因为要取消,当然也就递减引用
      ...
      return 1; #销毁线程成功
    }
  }
  HEAD_UNLOCK();
  return 0;
}
登入後複製

原生posix pthread 可以使用 ptread_cancel(tid) 在主執行緒中結束子執行緒。但是 Python 的線程庫不支援這樣做,理由是我們不應該強制地結束一個線程,這樣會帶來很多隱患,應該讓該線程自己結束自己。所以在 Python 中,建議的方法是在子線程中循環判斷一個標誌位,在主線程中改變該標誌位,子線程讀到標誌位改變,就結束自己。

類似這個邏輯:

def consumer_threading():
 t1_stop= threading.Event()
 t1 = threading.Thread(target=thread1, args=(1, t1_stop))
 
 t2_stop = threading.Event()
 t2 = threading.Thread(target=thread2, args=(2, t2_stop))
 
 time.sleep(duration)
 #stop the thread2
 t2_stop.set()
 
def thread1(arg1, stop_event):
 while(not stop_event.is_set()):
   #similar to time.sleep()
   stop_event.wait(time)
   pass
 
 
def thread2(arg1, stop_event):
 while(not stop_event.is_set()):
   stop_event.wait(time)
   pass
登入後複製

簡單的總結,雖然我們可以用ctypes裡的pystats來控制線程,但這種粗暴中斷線程的方法是不合理的。 請選用 自殺模式 !如果你的線程正在發生io阻塞,而不能判斷事件怎麼辦? 你的程式需要做優化了,最少在網路io層需要主動的timeout,避免一直的阻塞下去。

更多不要用強制方法殺掉python線程相關文章請關注PHP中文網!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何解決Linux終端中查看Python版本時遇到的權限問題? 如何解決Linux終端中查看Python版本時遇到的權限問題? Apr 01, 2025 pm 05:09 PM

Linux終端中查看Python版本時遇到權限問題的解決方法當你在Linux終端中嘗試查看Python的版本時,輸入python...

如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到? 如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到? Apr 02, 2025 am 07:15 AM

使用FiddlerEverywhere進行中間人讀取時如何避免被檢測到當你使用FiddlerEverywhere...

在Python中如何高效地將一個DataFrame的整列複製到另一個結構不同的DataFrame中? 在Python中如何高效地將一個DataFrame的整列複製到另一個結構不同的DataFrame中? Apr 01, 2025 pm 11:15 PM

在使用Python的pandas庫時,如何在兩個結構不同的DataFrame之間進行整列複製是一個常見的問題。假設我們有兩個Dat...

Uvicorn是如何在沒有serve_forever()的情況下持續監聽HTTP請求的? Uvicorn是如何在沒有serve_forever()的情況下持續監聽HTTP請求的? Apr 01, 2025 pm 10:51 PM

Uvicorn是如何持續監聽HTTP請求的? Uvicorn是一個基於ASGI的輕量級Web服務器,其核心功能之一便是監聽HTTP請求並進�...

如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎? 如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎? Apr 02, 2025 am 07:18 AM

如何在10小時內教計算機小白編程基礎?如果你只有10個小時來教計算機小白一些編程知識,你會選擇教些什麼�...

在Linux終端中使用python --version命令時如何解決權限問題? 在Linux終端中使用python --version命令時如何解決權限問題? Apr 02, 2025 am 06:36 AM

Linux終端中使用python...

如何繞過Investing.com的反爬蟲機制獲取新聞數據? 如何繞過Investing.com的反爬蟲機制獲取新聞數據? Apr 02, 2025 am 07:03 AM

攻克Investing.com的反爬蟲策略許多人嘗試爬取Investing.com(https://cn.investing.com/news/latest-news)的新聞數據時,常常�...

See all articles