web-dev-qa-db-ja.com

どのCPUコアでPythonプロセスが実行されていますか?

セットアップ

Python(Windows PC))でかなり複雑なソフトウェアを作成しました。私のソフトウェアは基本的に2つのPythonインタープリターシェルを起動します。最初のシェルが起動します(おそらく)main.pyファイルをダブルクリックすると、そのシェル内で他のスレッドが次のように開始されます。

    # Start TCP_thread
    TCP_thread = threading.Thread(name = 'TCP_loop', target = TCP_loop, args = (TCPsock,))
    TCP_thread.start()

    # Start UDP_thread
    UDP_thread = threading.Thread(name = 'UDP_loop', target = UDP_loop, args = (UDPsock,))
    TCP_thread.start()

Main_threadは、TCP_threadUDP_threadを開始します。これらは個別のスレッドですが、すべて単一のPython Shell。

Main_threadもサブプロセスを開始します。これは次の方法で行われます。

p = subprocess.Popen(['python', mySubprocessPath], Shell=True)

Pythonドキュメントから、このサブプロセスは同時に(!)別のPythonインタプリタセッション/シェル:このサブプロセスのMain_threadは完全に私のGUI専用であり、GUIはすべての通信に対してTCP_threadを開始します。

私は物事が少し複雑になることを知っています。したがって、セットアップ全体をこの図にまとめました。

enter image description here


このセットアップに関していくつか質問があります。ここにリストします。

質問1[Solved]

Pythonインタープリターは一度に1つのCPUコアのみを使用してすべてのスレッドを実行しますか?つまり、Python interpreter session 1(図から)は3つのスレッドすべてを実行します(Main_threadTCP_thread、およびUDP_thread)1つのCPUコアで?

回答:はい、これは本当です。 GIL(Global Interpreter Lock)は、すべてのスレッドが一度に1つのCPUコアで実行されるようにします。

質問2[未解決]

どのCPUコアであるかを追跡する方法はありますか?

質問3[部分的に解決した]

この質問では、threadsを忘れていますが、Pythonのsubprocessメカニズムに焦点を当てています。新しいサブプロセスの開始は、新しいPythonインタープリターinstance)の開始を意味します。これは正しいですか?

回答:はい、これは正しいです。最初は、次のコードが新しいPythonインタープリターインスタンスを作成するかどうかについて混乱がありました:

    p = subprocess.Popen(['python', mySubprocessPath], Shell = True)

問題は明確になりました。このコードは、実際に新しいPythonインタープリターインスタンスを開始します。

Python別個のPythonインタープリターインスタンスを別のCPUコアで実行するのに十分なほど賢いでしょうか?文も印刷しますか?

質問4[新しい質問]

コミュニティの議論は新しい質問を提起しました。新しいプロセスを生成するとき、明らかに2つのアプローチがあります(新しいPythonインタープリターインスタンス内):

    # Approach 1(a)
    p = subprocess.Popen(['python', mySubprocessPath], Shell = True)

    # Approach 1(b) (J.F. Sebastian)
    p = subprocess.Popen([sys.executable, mySubprocessPath])

    # Approach 2
    p = multiprocessing.Process(target=foo, args=(q,))

2番目のアプローチには、関数のみを対象とするという明らかな欠点がありますが、新しいPythonスクリプトを開く必要があります。とにかく、どちらのアプローチも達成方法が似ていますか?

35
K.Mulier

Q:Pythonインタープリターは、すべてのスレッドを実行するために一度に1つのCPUコアのみを使用するのは本当ですか?

いいえ。GILとCPUアフィニティは無関係な概念です。とにかく、GILはI/O操作のブロック中、C拡張内での長いCPU集中型計算中に解放できます。

GILでスレッドがブロックされている場合;おそらくどのCPUコア上にもないので、純粋なPythonマルチスレッドコードはCPython実装で一度に1つのCPUコアしか使用できないと言っても過言ではありません。

Q:つまり、Pythonインタープリターセッション1(図から)は3つのスレッド(Main_thread、TCP_thread、 UDP_thread)1つのCPUコアで?

CPythonが暗黙的にCPUアフィニティを管理するとは思わない。スレッドの実行場所の選択は、OSスケジューラに依存している可能性があります。 Pythonスレッドは、実際のOSスレッドの上に実装されます。

Q:またはPythonインタープリターはそれらを複数のコアに分散できますか?

使用可能なCPUの数を調べるには:

_>>> import os
>>> len(os.sched_getaffinity(0))
16
_

繰り返しますが、スレッドが異なるCPUでスケジュールされているかどうかは、Pythonインタープリターに依存しません。

Q:質問1の答えが「複数のコア」であると仮定すると、各スレッドが実行されているコアを追跡する方法はありますか?散発的な印刷文質問1の答えが「1つのコアのみ」である場合、どのコアかを追跡する方法はありますか?

あるタイムスロットから別のタイムスロットに特定のCPUが変わる可能性があります。 古いLinuxカーネルの_/proc/<pid>/task/<tid>/status_のようなものを見ることができます 。私のマシンでは、 _task_cpu_は_/proc/<pid>/stat_または_/proc/<pid>/task/<tid>/stat_ から読み取ることができます。

_>>> open("/proc/{pid}/stat".format(pid=os.getpid()), 'rb').read().split()[-14]
'4'
_

現在のポータブルソリューションについては、 psutil がそのような情報を公開しているかどうかを確認してください。

現在のプロセスを一連のCPUに制限できます。

_os.sched_setaffinity(0, {0}) # current process on 0-th core
_

Q:この質問では、スレッドについては忘れていますが、Pythonのサブプロセスメカニズムに焦点を当てています。新しいサブプロセスを開始すると、新しいPythonインタープリターセッション/シェルが開始されます。これは正しいです?

はい。 subprocessモジュールは、新しいOSプロセスを作成します。 python実行可能ファイルを実行すると、新しいPython interpeterが起動します。 bashスクリプトを実行すると、新しいPythonインタープリターは作成されません。つまり、bash実行可能ファイルを実行しても、新しいPythonインタープリター/セッション/などは開始されません。

Q:正しいと仮定すると、Pythonは、その別個のインタープリターセッションを別のCPUコアで実行できるようになりますか?おそらく散発的な印刷文を使用して、これを追跡する方法はありますか?

上記を参照してください(つまり、OSがスレッドの実行場所を決定し、スレッドの実行場所を公開するOS APIが存在する可能性があります)。

multiprocessing.Process(target=foo, args=(q,)).start()

_multiprocessing.Process_は、新しいOSプロセス(新しいPythonインタープリターを実行する)も作成します。

実際には、私のサブプロセスは別のファイルです。したがって、この例は私には機能しません。

Pythonはモジュールを使用してコードを整理します。コードが_another_file.py_にある場合は、メインモジュールで_import another_file_を使用し、_another_file.foo_を_multiprocessing.Process_に渡します。

それでも、p = subprocess.Popen(..)とどう比較しますか? subprocess.Popen(..)vs multiprocessing.Process(..)で新しいプロセスを開始するか(または 'pythonインタープリターインスタンス'と言う必要がありますか?)

multiprocessing.Process()はおそらくsubprocess.Popen()の上に実装されます。 multiprocessingthreading AP​​Iに似たAPIを提供し、pythonプロセス間の通信の詳細を抽象化します(送信するためにPythonオブジェクトをシリアル化する方法プロセス間)。

CPUを集中的に使用するタスクがない場合は、単一のプロセスでGUIスレッドとI/Oスレッドを実行できます。 CPUを集中的に使用する一連のタスクがある場合、複数のCPUを一度に使用するには、lxmlregexnumpy(または独自の Cython )を使用して作成されたもので、長い計算中にGILを解放したり、個別のプロセスにオフロードしたりできます(簡単な方法は、 _concurrent.futures_ )。

Q:コミュニティの議論で新しい質問が提起されました。新しいプロセスを(新しいPythonインタープリターインスタンス内で)生成するとき、明らかに2つのアプローチがあります。

_# Approach 1(a)
p = subprocess.Popen(['python', mySubprocessPath], Shell = True)

# Approach 1(b) (J.F. Sebastian)
p = subprocess.Popen([sys.executable, mySubprocessPath])

# Approach 2
p = multiprocessing.Process(target=foo, args=(q,))
_

"Approach 1(a)"はPOSIXでは間違っています(Windowsでは動作する可能性があります)。移植性のために、_cmd.exe_が必要なことがわかっている場合を除き、"Approach 1(b)"を使用します(この場合、正しいコマンドラインエスケープが使用されるように文字列を渡します)。

2番目のアプローチには、関数のみを対象とするという明らかな欠点がありますが、新しいPythonスクリプトを開く必要があります。とにかく、両方のアプローチは達成するものが似ていますか?

subprocessは新しいプロセスを作成します。anyプロセス。たとえば、bashスクリプトを実行できます。 multprocessingは、別のプロセスでPythonコードを実行するために使用されます。 import a Pythonモジュールの方が柔軟であり、スクリプトとして実行するよりも機能を実行します。 subprocess を使用してpythonスクリプトで入力を使用してpythonスクリプトを呼び出すを参照してください。

25
jfs

threading上に構築されるthreadモジュールを使用しているため。ドキュメントが示唆するように、OSの '' POSIXスレッド実装 ''pthreadを使用します。

  1. スレッドはPythonインタープリターの代わりにOSによって管理されます。そのため、答えはシステムのpthreadライブラリに依存します。ただし、CPythonはGILを使用して複数のスレッドの実行Pythonバイトコードは同じようにシーケンシャルに処理されますが、それでもpthreadライブラリに依存する異なるコアに分離できます。
  2. デバッガーを簡単に使用して、python.exeにアタッチします。たとえば、 GDBスレッドコマンド
  3. 質問1と同様に、新しいプロセスはOSによって管理され、おそらく異なるコアで実行されます。デバッガーまたはプロセスモニターを使用して表示します。詳細については、CreatProcess() documentation page にアクセスしてください。
3
gdlmx

1、2:実際のスレッドは3つありますが、CPythonではGILによって制限されているため、純粋なpythonを実行していると仮定すると、コードはCPU使用率を1つのコアのみが使用されているかのように表示します。

3:gdlmxで述べたように、スレッドを実行するコアを選択するのはOS次第ですが、本当に制御が必要な場合は、ctypes経由でネイティブAPIを使用してプロセスまたはスレッドアフィニティを設定できます。 Windowsを使用しているため、次のようになります。

_# This will run your subprocess on core#0 only
p = subprocess.Popen(['python', mySubprocessPath], Shell = True)
cpu_mask = 1
ctypes.windll.kernel32.SetProcessAffinityMask(p._handle, cpu_mask)
_

ここでは、単純にするためにプライベート_Popen._handle_を使用します。きれいな方法はbeOpenProcess(p.tid)などです。

はい、subprocessはpython別の新しいプロセスの他のすべてと同じように実行されます。

1
robyschek