マルチプロセッシング over threading の利点を理解しようとしています。 multiprocessing がGlobal Interpreter Lockを回避することはわかっていますが、他にどのような利点がありますか。 threading で同じことができないのですか。
threading
モジュールはスレッドを使用し、multiprocessing
モジュールはプロセスを使用します。違いは、スレッドは同じメモリ空間で実行されるのに対し、プロセスは別々のメモリを持つということです。これにより、マルチプロセッシングを使用してプロセス間でオブジェクトを共有することが少し難しくなります。スレッドは同じメモリを使用するため、予防措置を講じる必要があります。そうしないと、2つのスレッドが同時に同じメモリに書き込みます。これがグローバルインタプリタロックの目的です。
プロセスの生成は、スレッドの生成よりも少し遅くなります。それらが実行されると、それほど大きな違いはありません。
これが私が思いついたいくつかの賛否両論です。
multiprocessing
モジュールには、threading.Thread
のようなインターフェースを持つ便利な抽象化が含まれています。Queue
モジュールを使用して)コマンドキュー/メッセージポンプモデルに従わない場合は、同期プリミティブを手動で使用する必要があります(ロックの細分性には決定が必要です)。スレッディングの仕事はアプリケーションが応答することを可能にすることです。データベースに接続していて、ユーザー入力に応答する必要があるとします。スレッドがないと、データベース接続がビジーの場合、アプリケーションはユーザーに応答できません。データベース接続を別のスレッドに分割することで、アプリケーションの応答性を向上させることができます。また、両方のスレッドが同じプロセス内にあるため、同じデータ構造にアクセスできます - 優れたパフォーマンスと柔軟なソフトウェア設計。
GILのため、アプリケーションは実際には一度に2つのことをしているわけではありませんが、データベースのリソースロックを別のスレッドに配置して、CPU時間をユーザー操作と切り替えることができます。 CPU時間はスレッド間で配分されます。
マルチプロセッシングは、いつでも複数のことを実行したい場合があります。アプリケーションが6つのデータベースに接続し、各データセットに対して複雑な行列変換を実行する必要があるとします。 1つの接続がアイドル状態のときに別のスレッドにCPU時間がかかる可能性があるため、各ジョブを別々のスレッドに配置すると少し役に立ちますが、GILは1つのCPUのリソースしか使用しないため。各ジョブをマルチプロセッシングプロセスに入れることで、それぞれを独自のCPUで実行し、最大限の効率で実行できます。
主な利点は分離です。クラッシュしたプロセスは他のプロセスをダウンさせることはありませんが、クラッシュしたスレッドはおそらく他のスレッドを破壊するでしょう。
言及されていないもう一つのことは、速度が関係しているところであなたがどのOSを使っているかに依存するということです。 Windowsではプロセスはコストがかかるのでウィンドウではスレッドのほうが優れていますが、unixプロセスではウィンドウズよりも速いので、unixでプロセスを使用する方がはるかに安全で素早く生成できます。
他の答えは、マルチスレッドとマルチプロセッシングの側面に重点を置いていますが、PythonのGlobal Interpreter Lock(GIL)を考慮する必要があります。もっと数が多いと k)のスレッドが作成されます、一般的に彼らはパフォーマンスを向上させることはありません k まだシングルスレッドのアプリケーションとして実行されているためです。 GILは、すべてをロックアウトし、単一コアのみを利用して単一スレッドの実行のみを許可するグローバルロックです。 Numpy、Network、I/OなどのC拡張が使用されている場所、多くのバックグラウンド作業が行われてGILがリリースされている場所ではパフォーマンスが向上します。
そうするとき スレッディング pythonを使用すると、オペレーティングシステムレベルのスレッドは1つしかありませんが、pythonは、スレッド自体によって完全に管理され、本質的に単一のプロセスとして実行されている擬似スレッドを作成します。プリエンプションはこれらの疑似スレッド間で行われます。 CPUが最大容量で稼働している場合は、マルチプロセッシングに切り替えることをお勧めします。
自己完結型の実行インスタンスの場合は、代わりにプールを選択できます。しかし、データが重複している場合、プロセス間の通信が必要な場合はmultiprocessing.Process
を使用する必要があります。
質問で述べたように、PythonのMultiprocessingは、真の並列処理を実現するための唯一の現実的な方法です。 _ gil _ はスレッドの並列実行を妨げるため、マルチスレッドはこれを達成できません。
結果として、スレッド化はPythonでは必ずしも有用ではないかもしれません、そして実際に、あなたが達成しようとしているものによってはさらに悪いパフォーマンスをもたらすかもしれません。たとえば、gzipファイルの解凍や3Dレンダリング(CPUを多用するものなど)などの CPU-bound タスクを実行している場合、スレッド処理は実際にはヘルプよりもパフォーマンスを妨げる可能性があります。このような場合は、実際にはこのメソッドだけが並行して実行され、当面のタスクの重みを分散させるのに役立つので、Multiprocessingを使用することをお勧めします。 Multiprocessingはスクリプトのメモリを各サブプロセスにコピーすることを伴うので、これにはいくらかのオーバーヘッドがあるかもしれません。そして、それはより大きなサイズのアプリケーションにとって問題を引き起こすかもしれません。
ただし、マルチスレッドは、タスクが IO-bound の場合に役立ちます。たとえば、タスクの大部分が API-call の待機を伴う場合は、Multithreadingを使用することになります。のどかに座る。
TL; DR
Pythonドキュメントの引用符
Process vs ThreadsとGILに関するPythonドキュメントの重要な引用箇所を以下で強調しました。 CPythonのグローバルインタープリタロック(GIL)とは何ですか?
プロセスvsスレッド実験
違いをより具体的に示すために、少しベンチマークを行いました。
ベンチマークでは、 8ハイパースレッド CPU上でCPUとIOがさまざまな数のスレッドに対して動作するようにタイミングを合わせました。スレッドごとに提供される作業は常に同じです。スレッドが多いほど、提供される作業の合計が多くなります。
結果は次のとおりです。
結論:
cPUに制約のある作業では、マルチプロセッシングは常に高速です。おそらくGILによるものです。
IOバインド作業用。両方ともまったく同じ速度
私が8ハイパースレッドマシンを使っているので、スレッドは予想される8倍ではなく約4倍にしかスケールアップしません。
それとは対照的に、期待される8倍のスピードアップを達成するC POSIX CPUバウンドの仕事とは対照的です。 'real'、 'user'、および 'sys'はtime(1)の出力に何を意味しますか?
TODO:その理由はわかりませんが、他にも非効率なPythonが登場するはずです。
テストコード:
#!/usr/bin/env python3
import multiprocessing
import threading
import time
import sys
def cpu_func(result, niters):
'''
A useless CPU bound function.
'''
for i in range(niters):
result = (result * result * i + 2 * result * i * i + 3) % 10000000
return result
class CpuThread(threading.Thread):
def __init__(self, niters):
super().__init__()
self.niters = niters
self.result = 1
def run(self):
self.result = cpu_func(self.result, self.niters)
class CpuProcess(multiprocessing.Process):
def __init__(self, niters):
super().__init__()
self.niters = niters
self.result = 1
def run(self):
self.result = cpu_func(self.result, self.niters)
class IoThread(threading.Thread):
def __init__(self, sleep):
super().__init__()
self.sleep = sleep
self.result = self.sleep
def run(self):
time.sleep(self.sleep)
class IoProcess(multiprocessing.Process):
def __init__(self, sleep):
super().__init__()
self.sleep = sleep
self.result = self.sleep
def run(self):
time.sleep(self.sleep)
if __== '__main__':
cpu_n_iters = int(sys.argv[1])
sleep = 1
cpu_count = multiprocessing.cpu_count()
input_params = [
(CpuThread, cpu_n_iters),
(CpuProcess, cpu_n_iters),
(IoThread, sleep),
(IoProcess, sleep),
]
header = ['nthreads']
for thread_class, _ in input_params:
header.append(thread_class.__name__)
print(' '.join(header))
for nthreads in range(1, 2 * cpu_count):
results = [nthreads]
for thread_class, work_size in input_params:
start_time = time.time()
threads = []
for i in range(nthreads):
thread = thread_class(work_size)
threads.append(thread)
thread.start()
for i, thread in enumerate(threads):
thread.join()
results.append(time.time() - start_time)
print(' '.join('{:.6e}'.format(result) for result in results))
Ubuntu 18.10、Python 3.6.7、CPU搭載のLenovo ThinkPad P51ラップトップ:Intel Core i7-7820HQ CPU(4コア/ 8スレッド)、RAM:2xサムスンM471A2K43BB1-CRC(2x 16GiB)、SSD:サムスンMZVLB512HAJQ- 000L7(3,000 MB /秒).
プロセスは複数のスレッドを持つことができます。これらのスレッドはメモリを共有し、プロセス内の実行単位です。
プロセスはCPU上で実行されるため、スレッドは各プロセスの下に常駐しています。プロセスは独立して実行される個々のエンティティです。各プロセス間でデータや状態を共有したい場合は、Cache(redis, memcache)
、Files
、Database
などのメモリ保存ツールを使用できます。
_マルチプロセッシング_
- Pythonのマルチプロセッシングライブラリは、独立したメモリ空間、複数のCPUコアを使用し、CPythonのGIL制限を回避し、子プロセスは強制終了でき(プログラム内の関数呼び出しなど)、はるかに使いやすくなります。
- このモジュールに関する注意点の中には、メモリ使用量が大きくなること、およびIPCのオーバーヘッドが増えて少し複雑になることがあります。
_マルチスレッド_
- マルチスレッドライブラリは軽量で、メモリを共有し、レスポンシブUIを担当し、I/Oバウンドアプリケーションによく使用されます。
- このモジュールは殺害されず、GILの影響を受けます。
- 複数のスレッドが同じスペースの同じプロセスに存在し、各スレッドは特定のタスクを実行し、独自のコード、独自のスタックメモリ、命令ポインタ、および共有ヒープメモリを持ちます。
- スレッドにメモリリークがあると、他のスレッドや親プロセスに損傷を与える可能性があります。
Pythonを使ったマルチスレッドとマルチプロセッシングの例
Python 3は 並列タスクの起動 の機能を持っています。これにより作業が容易になります。
スレッドプーリング および プロセスプーリング があります。
以下は洞察を与えます:
ThreadPoolExecutorの例
import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# Start the load operations and mark each future with its URL
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
ProcessPoolExecutor
import concurrent.futures
import math
PRIMES = [
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419]
def is_prime(n):
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
def main():
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, prime in Zip(PRIMES, executor.map(is_prime, PRIMES)):
print('%d is prime: %s' % (number, prime))
if __== '__main__':
main()
私が大学で学ぶとき、上の答えのほとんどは正しいです。異なるプラットフォームでのプラクティスでは(常にpythonを使用します)、複数のスレッドを生成することは、1つのプロセスを生成することのようになります。違いは、1つのコアですべてを100%で処理するのではなく、複数のコアが負荷を共有することです。あなたが4コアのPC上で例えば10のスレッドを生成したのであれば、あなたはCPUパワーの25%しか得られなくなるでしょう!そして、もしあなたが10個のプロセスを生成するならば、あなたは100%のcpu処理で終わるでしょう(あなたが他の制限を持たないなら)。私はすべての新技術の専門家ではありません。自分自身の実際の経験の背景を持つIM