99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

Python線程如何銷毀?

發(fā)布時(shí)間:2017-02-23 15:45  回復(fù):0  查看:2890   最后回復(fù):2017-02-23 15:45  
不要試圖用強(qiáng)制方法殺掉一個(gè) python線程 ,這從服務(wù)設(shè)計(jì)上就存在不合理性。  多線程本用來(lái)任務(wù)的協(xié)作并發(fā),如果你使用強(qiáng)制手段干掉線程,那么很大幾率出現(xiàn)意想不到的bug 。 請(qǐng)記住一點(diǎn), 鎖資源不會(huì)因?yàn)榫€程退出而釋放鎖資源 !
  我們可以舉出兩個(gè)常見(jiàn)的  例子:
  1.  有個(gè) A 線程拿到了鎖,因?yàn)樗潜粡?qiáng)制干掉的,沒(méi)能及時(shí)的 release() 釋放鎖資源,那么導(dǎo)致所有的線程獲取資源是都被阻塞下去,這就是典型的死鎖場(chǎng)景。
  2.  在常見(jiàn)的生產(chǎn)消費(fèi)者的場(chǎng)景下,消費(fèi)者從任務(wù)隊(duì)列獲取任務(wù),但是被干掉后沒(méi)有把正在做的任務(wù)丟回隊(duì)列中,那么這就造成了數(shù)據(jù)丟失。
  下面是java python 終止線程的方法 :
  java 有三種方法可以使終止線程:
  1.  使用退出標(biāo)志,使線程正常退出,也就是當(dāng) run 方法完成后線程終止。
  2.  使用 stop 方法強(qiáng)行終止線程(不推薦使用,因?yàn)?/span> stop suspend 、 resume 一樣,也可能發(fā)生不可預(yù)料的結(jié)果)。
  3.  使用 interrupt 方法中斷線程。
  python 可以有兩種方法:
  1.  退出標(biāo)記
  2.  使用 ctypes 強(qiáng)行殺掉線程
  不管是python 還是 java 環(huán)境下,理想的停止退出線程方法是 讓線程自個(gè)自殺,所謂的線程自殺就是 你給他一個(gè)標(biāo)志位,他退出線程。
  下面我們會(huì)采用多種方法來(lái)測(cè)試  停止python 線程的異常情況。 我們查看一個(gè)進(jìn)程所有的執(zhí)行線程 進(jìn)程是用過(guò)掌控資源,線程是用作調(diào)度單元,進(jìn)程要被調(diào)度執(zhí)行必須要有一個(gè)線程,默認(rèn)的線程和進(jìn)程的 pid 一樣的。
 # xiaorui.cc

ps -mp 31449 -o THREAD,tid

USER    %CPUPRISCNTWCHAN  USER SYSTEM  TID
root      0.0  -    - -        -      -    -
root      0.0  19    - poll_s    -      - 31449
root      0.0  19    - poll_s    -      - 31450
  獲取到了進(jìn)程所有的線程后,通過(guò)strace 得知  31450  是需要我們 kill 的線程 id ,當(dāng)我們 kill 的時(shí)候,會(huì)出現(xiàn)整個(gè)進(jìn)程都崩潰的情況。 在多線程環(huán)境下,產(chǎn)生的信號(hào)是傳遞給整個(gè)進(jìn)程的,一般而言,所有線程都有機(jī)會(huì)收到這個(gè)信號(hào),進(jìn)程在收到信號(hào)的的線程上下文執(zhí)行信號(hào)處理函數(shù),具體是哪個(gè)線程執(zhí)行的難以獲知。也就是說(shuō),信號(hào)會(huì)隨機(jī)發(fā)個(gè)該進(jìn)程的一個(gè)線程。
 strace -p <spanstyle="font-size:14px;line-height:21px;">31450</span> Process <spanstyle="font-size:14px;line-height:21px;">31450</span> attached - interrupttoquit
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 (Toberestarted)
--- SIGTERM (Terminated) @ 0 (0) ---
Process <spanstyle="font-size:14px;line-height:21px;">31450</span> detached
  上面出現(xiàn)的問(wèn)題其實(shí)跟pthread 的說(shuō)明是一致的。 當(dāng)我們?cè)?/span> python 代碼里加入  signal  信號(hào)處理函數(shù)后,回調(diào)函數(shù)可以防止整個(gè)進(jìn)程的退出,那么問(wèn)題來(lái)了,通過(guò)信號(hào)函數(shù)不能識(shí)別你要干掉哪一個(gè)線程,也就是說(shuō),不能精準(zhǔn)的干掉某個(gè)線程。你雖然把信號(hào)發(fā)給 31450 線程 id ,但是信號(hào)受理人是所屬進(jìn)程的任何一個(gè),另外傳給信號(hào)處理函數(shù)的參數(shù)只有信號(hào)數(shù)和信號(hào) stack 而已,可有可無(wú)的。
加了信號(hào)處理后,不會(huì)退出進(jìn)程
select(0, NULL, NULL, NULL, {1, 0})    = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})    = ? ERESTARTNOHAND (Toberestarted)
--- SIGTERM (Terminated) @ 0 (0) ---
rt_sigreturn(0xffffffff)                = -1 EINTR (Interruptedsystemcall)
select(0, NULL, NULL, NULL, {1, 0})    = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})    = 0 (Timeout)
  如果想從外部通知?dú)⒌裟硞€(gè)線程,那么可以構(gòu)建使用rpc 服務(wù),或者別的方式通信, signal 信號(hào)不可以,因?yàn)闊o(wú)法無(wú)法傳遞更多的信息。
  python 的線程不是模擬的,是真實(shí)的內(nèi)核線程,內(nèi)核調(diào)用 pthread 方法,但 Python 上層沒(méi)有提供關(guān)閉線程的方法,這就需要我們自己把握了。強(qiáng)烈推薦使用 event  或者 自定義標(biāo)志位的方法, 如果非要強(qiáng)制殺掉線程,那么可以用 python ctypes PyThreadState SetAsyncExc  方法強(qiáng)制退出,這樣對(duì)于運(yùn)行的 python 服務(wù)沒(méi)有什么影響。
  該函數(shù)的實(shí)現(xiàn)原理比較簡(jiǎn)單,其實(shí)也是在python 虛擬機(jī)里做個(gè)標(biāo)示位,然后由虛擬機(jī)運(yùn)行一個(gè)異常來(lái)取消線程,虛擬機(jī)會(huì)幫你做好 try cache 。 切記不要在外部殺掉 python 的某個(gè)線程,雖然你能通過(guò) ctypes 找到線程 id ,但是你直接 kill 會(huì)干掉整個(gè)進(jìn)程的。
  下面的代碼是  ctypes  殺掉線程的樣例,不推薦使用,因?yàn)樘直┝?/span> .
  # xiaorui.cc
  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")
  咱們簡(jiǎn)單look 一下 PyThreadState 源代碼,總而言之觸發(fā)線程的異常模式。 有興趣的人可以閱讀  python pystate.c  的設(shè)計(jì),配合著 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); # 增加該對(duì)象的引用數(shù)
  p->async_exc = exc; #  更為 exc 模式
  HEAD_UNLOCK();
  Py_XDECREF(old_exc); #  因?yàn)橐∠?,?dāng)然也就遞減引用
  ...
  return 1; # 銷毀線程成功
  }
  }
  HEAD_UNLOCK();
  return 0;
  }
  原生posix pthread  可以使用  ptread_cancel(tid)  在主線程中結(jié)束子線程。但是  Python  的線程庫(kù)不支持這樣做,理由是我們不應(yīng)該強(qiáng)制地結(jié)束一個(gè)線程,這樣會(huì)帶來(lái)很多隱患,應(yīng)該讓該線程自己結(jié)束自己。所以在  Python  中,推薦的方法是在子線程中循環(huán)判斷一個(gè)標(biāo)志位,在主線程中改變?cè)摌?biāo)志位,子線程讀到標(biāo)志位改變,就結(jié)束自己。
  類似這個(gè)邏輯:
  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
  簡(jiǎn)單的總結(jié),雖然我們可以用ctypes 里的 pystats 來(lái)控制線程,但這種粗暴中斷線程的方法是不合理的。 請(qǐng)選用 自殺模式 !如果你的線程正在發(fā)生 io 阻塞,而不能判斷事件怎么辦? 你的程序需要做優(yōu)化了,最少在網(wǎng)絡(luò) io 層需要有主動(dòng)的 timeout ,避免一直的阻塞下去。

來(lái)源: 峰云就她了
您還未登錄,請(qǐng)先登錄

熱門帖子

最新帖子

?