スレッドはPythonでどのように機能しますか?また、Pythonスレッド特有の一般的な落とし穴は何ですか?


85

私はPythonでスレッドがどのように機能するかについて頭を悩ませてきましたが、スレッドがどのように機能するかについての適切な情報を見つけるのは困難です。リンクか何かが足りないかもしれませんが、公式のドキュメントはこのテーマについてあまり徹底していないようで、良い記事を見つけることができませんでした。

私の知る限り、一度に実行できるスレッドは1つだけで、アクティブなスレッドは10命令ごとに切り替わりますか?

良い説明はどこにありますか、それとも提供できますか?また、Pythonでスレッドを使用しているときに発生する一般的な問題を知っておくと非常に便利です。

回答:


51

はい、グローバルインタープリターロック(GIL)のため、一度に実行できるスレッドは1つだけです。これに関するいくつかの洞察とのいくつかのリンクがあります:

最後のリンクから興味深い引用:

それが何を意味するのか説明させてください。スレッドは同じ仮想マシン内で実行されるため、同じ物理マシンで実行されます。プロセスは、同じ物理マシンまたは別の物理マシンで実行できます。スレッドを中心にアプリケーションを設計する場合、複数のマシンにアクセスするために何もしていません。したがって、1台のマシン上にあるコアの数に合わせて拡張できますが(時間の経過とともにかなりの数になります)、実際にWebスケールに到達するには、とにかく複数のマシンの問題を解決する必要があります。

マルチコアを使用する場合、pyprocessingは実際の並列化を行うためのプロセスベースのAPIを定義します。PEPはまた、いくつかの興味深いベンチマークを含んでいます。


1
本当にsmoothspanの見積もりについてのコメント:確かに、Pythonスレッドは、マシンに複数のコアがある場合でも、効果的に1つのコアに制限しますか?次のスレッドはコンテキストスイッチなしで実行できるため、マルチコアのメリットがあるかもしれませんが、Pythonスレッドは一度に1つを超えるコアを使用することはできません。
James Brady

2
正解です。Pythonスレッドは実質的に1つのコアに制限されています。ただし、CモジュールがGILと適切に相互作用し、独自のネイティブスレッドを実行する場合を除きます。
アラファンギオン2009

実際には、複数のコアは、スレッドが作るより少ない解約の多くは、各スレッドはGILにアクセスできるかどうかをチェックしてありますよう効率的。新しいGILを使用しても、パフォーマンスはさらに悪化します... dabeaz.com/python/NewGIL.pdf
基本的な

2
GILの考慮事項は、すべての通訳者に適用されるわけではないことに注意してください。私の知る限り、IronPythonとJythonの両方がGILなしで機能し、それらのコードがマルチプロセッサハードウェアをより効果的に使用できるようにしています。Arafangionが述べたように、Pythonデータ項目へのアクセスを必要としないコードがロックを解放し、戻る前に再度取得した場合、CPythonインタープリターは適切にマルチスレッドで実行することもできます。
holdenweb 2014

Pythonのスレッド間でコンテキストスイッチが発生する原因は何ですか?タイマー割り込みに基づいていますか?ブロッキング、または特定のイールドコール?
CMCDragonkai 2016年

36

Pythonはスレッド化するのがかなり簡単な言語ですが、注意点があります。あなたが知る必要がある最大のことはグローバルインタプリタロックです。これにより、1つのスレッドのみがインタープリターにアクセスできます。これは2つのことを意味します:1)Pythonでlockステートメントを使用することはめったにありません。2)マルチプロセッサシステムを利用したい場合は、別々のプロセスを使用する必要があります。編集:GILも回避したい場合は、コードの一部をC / C ++に配置できることも指摘しておく必要があります。

したがって、スレッドを使用する理由を再検討する必要があります。デュアルコアアーキテクチャを利用するためにアプリを並列化する場合は、アプリを複数のプロセスに分割することを検討する必要があります。

応答性を向上させたい場合は、スレッドの使用を検討する必要があります。ただし、他の選択肢、つまりマイクロスレッディングがあります。調べる必要のあるフレームワークもいくつかあります。


@ JS-修正されました。そのリストはとにかく時代遅れでした。
ジェイソンベイカー

マルチコアシステムを利用するには、複数のプロセスが必要であり、それに伴うすべてのオーバーヘッドが必要であるというのは、私には間違っていると感じています。32個の論理コアを備えたサーバーがいくつかあるので、それらを効率的に使用するには32個のプロセスが必要ですか?狂気
基本的な

@ Basic-最近のプロセスの開始とスレッドの開始のオーバーヘッドは最小限です。1秒あたり数千のクエリについて話していると、問題が発生する可能性があると思いますが、そもそも、このような忙しいサービスにPythonを選択するかどうか疑問に思います。
ジェイソンベイカー

20

以下は、基本的なスレッドのサンプルです。20個のスレッドを生成します。各スレッドはそのスレッド番号を出力します。それを実行し、それらが印刷される順序を観察します。

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

あなたが示唆したように、Pythonスレッドはタイムスライスによって実装されます。これが「並列」効果を得る方法です。

私の例では、Fooクラスがスレッドを拡張し、次にrunメソッドを実装します。このメソッドは、スレッドで実行するコードの行き先です。スレッドを開始するstart()には、スレッドオブジェクトを呼び出します。これにより、runメソッドが自動的に呼び出されます。

もちろん、これは非常に基本的なことです。最終的には、スレッドの同期とメッセージパッシングのためのセマフォ、ミューテックス、およびロックについて学びたいと思うでしょう。


10

個々のワーカーがI / Oバウンド操作を実行している場合は、Pythonでスレッドを使用します。マシン上の複数のコアにまたがってスケーリングしようとしている場合は、Pythonに適したIPCフレームワークを見つけるか、別の言語を選択してください。


6

注: 私が言及threadするところはどこでも、私は特にPythonのスレッドを意味します、明示的に述べるまでます。

あなたがから来ている場合、スレッドはPythonで少し異なって動作します C/C++バックグラウンドいる。Pythonでは、一度に1つのスレッドのみが実行状態になります。つまり、Pythonのスレッドは、設計上、複数のコアで並列に実行することができないため、複数の処理コアの能力を実際に活用することはできません。

Pythonのメモリ管理はスレッドセーフではないため、各スレッドはPythonインタープリターのデータ構造への排他的アクセスを必要とします。この排他的アクセスは(グローバルインタープリターロック)と呼ばれるメカニズムによって取得されます。GIL

Why does python use GIL?

複数のスレッドがインタプリタの状態に同時にアクセスしてインタプリタの状態を破壊するのを防ぐため。

アイデアは、スレッドが実行されているときはいつでも(メインスレッドであっても)、GILが取得され、事前定義された時間の後に、GILが現在のスレッドによって解放され、他のスレッド(存在する場合)によって再取得されます。

Why not simply remove GIL?

GILを削除することは不可能ではありません。削除することで、アクセスをシリアル化するためにインタープリター内に複数のロックを配置することになり、単一のスレッドアプリケーションでもパフォーマンスが低下します。

したがって、GILを削除するコストは、シングルスレッドアプリケーションのパフォーマンスの低下によって支払われますが、これは決して望ましくありません。

So when does thread switching occurs in python?

スレッド切り替えはGILがリリースされると発生しますが、GILはいつリリースされますか?考慮すべき2つのシナリオがあります。

スレッドがCPUバウンド操作(画像処理など)を実行している場合。

古いバージョンのpythonでは、スレッドの切り替えはpython命令の固定数の後に発生100していました。デフォルトではに設定されていました。単一の命令の実行に時間がかかったため、切り替えをいつ実行するかを決定するのはあまり適切なポリシーではないことが判明しました。ミリ秒から1秒まで非常に乱暴になります。したがって、毎回GILをリリースします。100に発生する可能性があるため、実行にかかる時間に関係なく、命令のすることは不適切なポリシーです。

新しいバージョンでは、スレッドを切り替えるためのメトリックとして命令カウントを使用する代わりに、構成可能な時間間隔が使用されます。デフォルトの切り替え間隔は5ミリ秒sys.getswitchinterval()です。を使用して、現在の切り替え間隔を取得できます。これは、を使用して変更できますsys.setswitchinterval()

スレッドがIOバウンド操作を実行している場合(ファイルシステムアクセスまたは
ネットワークIOなど)

GILは、スレッドがIO操作の完了を待機しているときはいつでも解放されます。

Which thread to switch to next?

インタプリタには独自のスケジューラがありません。間隔の終わりにどのスレッドがスケジュールされるかは、オペレーティングシステムの決定です。。


3

GILの簡単な解決策の1つは、マルチプロセッシングモジュールです。スレッドモジュールの代わりにドロップインとして使用できますが、スレッドの代わりに複数のインタープリタープロセスを使用します。このため、単純なもののプレーンスレッドよりも少しオーバーヘッドがありますが、必要に応じて実際の並列化の利点が得られます。また、複数の物理マシンに簡単に拡張できます。

私がさらに検討するよりも本当に大規模な並列化が必要な場合でも、より包括的なフレームワークの実装に必要なすべての作業を行わずに、1台のコンピューターのすべてのコアまたはいくつかの異なるコアにスケーリングしたい場合は、これはあなたのためです。


2

GILは、複数のタスクの外観を表示するために、頻繁にポーリングするように設定されていることを覚えておいてください。この設定は微調整できますが、スレッドが実行している作業が必要であるか、多くのコンテキストスイッチが問題を引き起こす可能性があることを提案します。

私は、プロセッサ上に複数の親を提案し、同じコア上で同様のジョブを維持しようとするところまで行きます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.