ホームページ  >  記事  >  バックエンド開発  >  Python スレッドを強制終了しないでください

Python スレッドを強制終了しないでください

高洛峰
高洛峰オリジナル
2017-02-28 09:06:341643ブラウズ

前書き:

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

シグナル処理を追加した後、プロセスは終了しません

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 の上位層にはスレッドを閉じるメソッドが用意されていないため、自分で制御する必要があります。スレッドを強制的に強制終了する必要がある場合は、Python ctypes PyThreadState SetAsyncExc メソッドを使用して強制終了できます。これは、実行中の Python サービスには影響しません。

実際、この関数の実装原理は比較的単純で、Python 仮想マシンにマークを付けると、仮想マシンが例外を実行してスレッドをキャンセルします。キャッシュ。 Python のスレッドを外部から強制終了しないように注意してください。スレッド ID は ctypes を通じて確認できますが、スレッドを直接強制終了するとプロセス全体が強制終了されます。

次のコードは ctypes を使用してスレッドを強制終了する例です。これはあまりにも失礼なのでお勧めできません。

簡単に PyThreadState のソース コードを見てみましょう。スレッドのモードがトリガーされます。 興味のある方は、Python pystate.c の設計を読んで、YouTube のビデオで共有してください。


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")

ネイティブ posix pthread は、ptread_cancel(tid) を使用してメインスレッド内の子スレッドを終了できます。ただし、Python のスレッド ライブラリはこれをサポートしていません。これは、多くの隠れた危険をもたらすため、スレッドを強制的に終了すべきではなく、スレッド自体を終了できるようにする必要があるためです。したがって、Python では、サブスレッドをループしてフラグを決定し、メインスレッドでフラグを変更し、サブスレッドがフラグの変更を読み取ったときに自体を終了する方法をお勧めします。

このロジックに似ています:

 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;
}

簡単にまとめると、ctypes で pystats を使用してスレッドを制御することはできますが、スレッドを無作法に中断するこの方法は非合理的です。 自殺モードを使用してください!スレッドが IO をブロックしていて、イベントを判断できない場合はどうすればよいでしょうか? 継続的なブロックを避けるために、プログラムを最適化する必要があります。少なくともネットワーク IO 層でアクティブなタイムアウトを設定する必要があります。

Python スレッドを強制終了する強制メソッドを使用しないことに関するその他の関連記事については、PHP 中国語 Web サイトに注目してください。

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