ホームページ  >  記事  >  バックエンド開発  >  Python スレッドを非強制終了する方法の紹介

Python スレッドを非強制終了する方法の紹介

Y2J
Y2Jオリジナル
2017-05-06 14:51:231679ブラウズ

この記事では、Python スレッドを強制的に強制終了することについていくつかの経験と教訓を共有します。スレッドを強制的に強制終了すると、予期しないバグが発生する可能性が高くなります。 スレッドが終了してもロック リソースは解放されないことに注意してください。

前書き:

Python スレッドを強制的に強制終了しようとしないでください。これはサービス設計の観点からは不合理です。 マルチスレッドはタスクの共同同時実行に使用されます。スレッドを強制的に強制終了すると、予期しないバグが発生する可能性が高くなります。スレッドが終了してもロック リソースは解放されないことに注意してください。

2 つの一般的な例を挙げることができます:

1. スレッド A は、強制的に強制終了され、release() でロック リソースを解放できなかったため、ロックを取得しました。そのため、すべてのスレッドがリソースを取得しました。これは典型的なデッドロック シナリオです。

2. 一般的な運用環境とコンシューマのシナリオでは、コンシューマはタスク キューからタスクを取得しますが、実行中のタスクが強制終了された後にキューに戻されないため、データが失われます。

Java と Python でスレッドを終了する方法は次のとおりです:

Java にはスレッドを終了する 3 つの方法があります:

1. スレッドを正常に終了するには、終了フラグを使用します。つまり、スレッドが終了したときにスレッドが終了します。 runメソッドが完了しました。
2. stop メソッドを使用してスレッドを強制的に終了します (停止はサスペンドと再開と同じであり、予期しない結果が発生する可能性があるため、お勧めしません)。
3. スレッドを中断するには、interrupt メソッドを使用します。

Python には 2 つのメソッドがあります:

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 を通じて、kill する必要があるスレッド ID が 31450 であることがわかります。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 コードに追加すると、コールバック関数 がプロセス全体の終了を妨げる可能性があります。つまり、シグナル関数はどのスレッドを強制終了するかを正確に識別できません。特定のスレッドを強制終了します。シグナルをスレッド ID 31450 に送信しますが、シグナル アクセプターはシグナルが属するプロセスのいずれかになります。また、シグナル処理関数に渡されるパラメーターはシグナルの数とシグナル スタックのみであり、これらはオプションです。 。 シグナル処理を追加した後、プロセスは終了しません

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)

外部からスレッドをkillしたい場合は、rpcサービスを構築して使用するか、シグナルシグナルを渡すことができないため、他の方法で通信できます。 。 情報。

Python スレッドはシミュレートされたものではなく、実際のカーネル スレッドです。カーネルは pthread メソッドを呼び出しますが、Python の上位層にはスレッドを閉じるメソッドが用意されていないため、自分で制御する必要があります。スレッドを強制的に終了する必要がある場合は、イベントまたはカスタム フラグ ビット メソッドを使用することを強くお勧めします。Python ctypes PyThreadState

Set

AsyncExc メソッドを使用して強制終了できます。これは、実行中の Python サービスには影響しません。
実際、この関数の実装原理は比較的単純で、Python 仮想マシンにフラグを設定すると、仮想マシンが例外を実行してスレッドをキャンセルします。

キャッシュ

を試してください。 Python のスレッドを外部から強制終了しないように注意してください。スレッド ID は ctypes を通じて確認できますが、スレッドを直接強制終了するとプロセス全体が強制終了されます。 次のコードは 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")

簡単に 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,避免一直的阻塞下去。

【相关推荐】

1. Python免费视频教程

2. Python基础入门教程

3. Python面向对象视频教程

以上がPython スレッドを非強制終了する方法の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。