実例コード解説Pythonスレッド池


仕事が多すぎて、量が多すぎると、効率を上げる一番簡単な方法はマルチスレッドで処理することです。例えば、万を超えるページの中の特定のデータを取得したり、データをはったり洗ったりする作業を異なるスレッドに渡して処理することです。つまり、生産者の消費者モードは、典型的なマルチスレッドの使用シーンです。
スレッドの数が多いほど、プログラムの実行効率が速くなるということですか?
明らかに違います。スレッドは一つのオブジェクトであり、リソースを占用する必要があります。スレッドの数が多すぎるとリソースが消耗されます。スレッド間のコンテキスト切り替えも大きなオーバーヘッドです。だから、多すぎるスレッドを開発するとプログラムの実行効率が向上するだけでなく、逆効果でプログラムが遅くなることがあります。
したがって、マルチスレッドの数をどのように決定するかは、マルチスレッドプログラミングにおいて非常に重要な問題である。長年の模索を経て、業界は基本的にデフォルトの基準になっています。
CPUの密集型の計算シーンに対しては、スレッド数をCPUコア数に設定することが最適であり、各CPUコアの性能を極限まで圧搾することができますが、工程では、スレッド数はCPUコア数+1として設定されています。このように、あるスレッドが未知の原因でブロックされている時に、余分なスレッドが完全にトップに上がることができます。
I/O密集型の応用には、CPU計算の時間消費とI/Oの時間消費比を考慮する必要がある。I/O消耗時とCPU消耗時が1:1の場合、AスレッドがI/O操作を行う場合、BスレッドがI/O操作を行う場合、AスレッドがCPU計算タスクを実行し、CPUとI/Oの利用率は100%を得て、完璧です。最適スレッド数=CPUコア数*[1+(I/O消耗時/CPU消耗時]と考えられます。
スレッドの池
普段は自分でマルチスレッドプログラムを書く時は、ほとんどが直接Threadを呼び出すだけでいいです。実際にスレッドを作るのはそんなに簡単ではありません。メモリを割り当てる必要があります。また、スレッドはオペレーティングシステムのカーネルのAPIを呼び出す必要があります。そして、オペレーティングシステムはスレッドに一連のリソースを割り当てる必要があります。プロセスは複雑です。頻繁にスレッドを作成したり、廃棄したりするのは避けるべきです。
自分が普段マルチスレッドコードを書いているモードを思い出してください。タスクが来たら直接スレッドを作成してタスクを実行します。タスクが終わったらスレッドは消滅します。そしてまた缲り返し始めた。いくつかのタスクがスレッドを作成しました。このモードはハードの資源を浪費します。
このような問題を避けるにはどうすればいいですか?スレッド池が役に立ちます。
実はスレッド池は生産者消費者モードの最適な実践であり、スレッド池が初期化されると自動的に指定された数のスレッドが作成され、ミッションが到着した時に直接にスレッドから空きスレッドを取って使えばいいです。タスクが実行された時にスレッドが消滅するのではなく、直接にアイドル状態に入り、次のタスクを待ち続けます。タスクが増加するにつれて、スレッド池の利用可能スレッドは徐々に減少します。0に減少すると、タスクは待つ必要があります。
pythonにおいてスレッド池を使用する方法は、第三者ライブラリthreadpoolに基づくものと、python 3に基づいて新たに導入されたライブラリconcurrent.futures.ThreadPoolExecutorがある。ここで紹介します。
threadpool方式
threadpoolを使う前に、まずインストールしてください。長い間私達の文章を読んでいます。すぐに解決できると信じています。次のコマンドをコマンドラインで実行すればいいです。

pip install threadpool
以下は簡易スレッドプールのテンプレートを使用して、関数sayhelloを作成し、2サイズのスレッドプールを作成しました。つまり、スレッドプールは全部で2つのアクティブスレッドがあります。
最後に pool.putRequest() を通じてスレッドホールドにジョブを投げ、pool.wait() はスレッドの終了を待つ。同時に、私たちはまた、コールバック関数を定義し、タスクの結果を得ることができます。
その結果、スレッド池には2つのスレッドしかないことが分かりました。それぞれThread-1Thread-2です。

import time
import threadpool
import threading

def sayhello(name):
  print("%s say Hello to %s" % (threading.current_thread().getName(), name));
  time.sleep(1)
  return name

def callback(request, result): #     ,      
  print("callback result = %s" % result)

name_list =['admin','root','scott','tiger']
start_time = time.time()
pool = threadpool.ThreadPool(2) #      
requests = threadpool.makeRequests(sayhello, name_list, callback) #     
[pool.putRequest(req) for req in requests] #     
pool.wait() 
print('%s cost %d second' % (threading.current_thread().getName(), time.time()-start_time))

##       
Thread-1 say Hello to admin
Thread-2 say Hello to root
Thread-1 say Hello to scott
Thread-2 say Hello to tiger
callback result = admin
callback result = root
callback result = tiger
callback result = scott
MainThread cost 2 second
ThreadPool Exector方式ThreadPoolExecutor はpython 3が新しく導入したライブラリで、具体的な使い方は threadpool と大同小異で、同じように容量が2のスレッド池を作成し、4つのタスクを提出します。ただし、ここでは、それぞれsubmit as_completedを介してジョブの返却結果を提出し、取得する。
出力結果によっても,2つのスレッド池の実現形態においてスレッドの命名方式が一致しないことを示した。

import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed

def sayhello(name):
  print("%s say Hello to %s" % (threading.current_thread().getName(), name));
  time.sleep(1)
  return name

name_list =['admin','root','scott','tiger']
start_time = time.time()
with ThreadPoolExecutor(2) as executor: #    ThreadPoolExecutor 
  future_list = [executor.submit(sayhello, name) for name in name_list] #     

for future in as_completed(future_list):
  result = future.result() #       
  print("%s get result : %s" % (threading.current_thread().getName(), result))

print('%s cost %d second' % (threading.current_thread().getName(), time.time()-start_time))

##       
ThreadPoolExecutor-0_0 say Hello to admin
ThreadPoolExecutor-0_1 say Hello to root
ThreadPoolExecutor-0_0 say Hello to scott
ThreadPoolExecutor-0_1 say Hello to tiger
MainThread get result : root
MainThread get result : tiger
MainThread get result : scott
MainThread get result : admin
MainThread cost 2 second
スレッドのまとめ
本論文では、一般的に使われている2つのスレッドプールの実現方法を紹介します。マルチスレッドプログラムでは、自分でスレッドを作成しないでください。しかし、内蔵されているスレッドプールは、安全性、性能、利便性の観点から、基本的に最適です。また、スレッドプールは私たちのために多くの追加作業をしてくれました。例えば、タスクキューのメンテナンス、スレッド廃棄時の資源の回収などは、開発者が関心を持つ必要がありません。他の仕事に関心を持つ必要はありません。これは私たちの仕事の効率と使用感を大いに高めます。
もちろん自分の持っているスレッド池も完全無欠ではなく、少なくとも一時的に動的な追加タスクを提供する入り口が出てきません。また、設計の面ではあまり柔軟ではないです。例えば、スレッド池は一つの核心の数量だけを維持したいです。しかし、タスクが多すぎると、新しいスレッド(閾値はカスタマイズ可能)を追加で作成できます。処理が終わったら、これらの余分なスレッドは自動的に破壊されます。これはできません。
コードアドレス
https://github.com/JustDoPython/python-100-day/tree/master/day-053
参考資料
https://chrisarndt.de/projects/threadpool/api/
以上はコードの実例です。Pythonスレッド池の詳細について説明します。Pythonスレッド池に関する資料は他の関連記事に注目してください。