ホームページ >バックエンド開発 >Python チュートリアル >Pythonマルチスレッド
まず最初に、マルチスレッドのアプリケーション シナリオについて説明します。Python が複数のタスクを処理する場合、これらのタスクは本質的に非同期であり、各トランザクションの実行順序は不確実で、ランダムで、予測できない場合があります。コンピューティング集約型のタスクは、複数のサブタスクに分割して順次実行することも、マルチスレッド方式で処理することもできます。ただし、I/O 集中型のタスクは、シングルスレッドで処理するのが困難です。マルチスレッドを使用しない場合は、1 つ以上のタイマーでのみ処理できます。
プロセスとスレッドについて話しましょう: プロセス (重量プロセスとも呼ばれます) はプログラムの実行です。centos と同様に、 ps -aux | で何かを実行すると、常にそれ自体によって生成されたプロセスが存在します。この grep プロセスでは、各プロセスは独自のアドレス空間、メモリ、データ スタック、および実行軌跡を記録するその他の補助データを持っているため、各プロセスは情報を直接共有できず、プロセス間通信 (IPC) のみを使用できます。
スレッド (軽量プロセス) とプロセスの最大の違いは、すべてのスレッドが同じプロセスで実行され、同じ動作環境を共有し、同じデータ空間を共有することです。したがって、スレッドはプロセスよりも簡単にデータを共有し、相互に通信し、トランザクションを同時に実行できます。
メモリプロセスとスレッドの関係を簡単に理解するために、CPU を引越し会社に例えると、この引越し会社は最初、使用する車 (プロセス) を 1 台しか持っていませんでした。非常に貧しいので、従業員は 1 人 (シングルスレッド)、その後、この引っ越し会社は 1 日に最大 5 軒しか移動できませんでした。その後、上司は車を買う代わりに、さらに n 人の従業員 (マルチスレッド) を雇いました。 -スレッド化されているため、各従業員は一度に 1 軒だけ移動し、その後休憩し、車を放棄し、他の従業員が次の家に移動するように手配されます。これでは実際には効率が向上するわけではありませんが、コストが増加します。 , これは、GIL (Global Interpreter Lock) グローバル インタープリタ ロックがスレッド セーフ (データを安全に読み取れることを保証する) を保証するためです。これは、CPU 上で同時に 1 つのスレッドしか実行できないためです。 Python、つまり、実行環境にデュアル CPU があり、Python 仮想マシンが 1 つの CPU のみを使用する場合でも、GIL によって CPython が物理マルチコアのパフォーマンスを利用して操作を高速化できなくなることを意味します。詳細な説明 (歴史に残された問題、ハードウェアの発展が速すぎる) については、このブログを参照してください:
http://blog.sina.com.cn/s/blog_64ecfc2f0102uzzf.html
Python コア プログラミング、著者は次の理由から、スレッド モジュールではなくスレッド モジュールを使用することを強くお勧めします:
1. メイン スレッドが終了すると、他のすべてのスレッドはクリアされずに終了します。スレッド モジュールはすべての子の安全な終了を保護できません。スレッド。つまり、スレッド モジュールはデーモンをサポートしません。
2. スレッドモジュールの属性がスレッド化と競合する可能性があります。
3. 低レベルのスレッド モジュールには同期プリミティブがほとんどありません (実際には 1 つだけあり、スリープである必要があります)。
1. Thread モジュール
以下は、GIL を使用しない場合と GIL を使用する場合の 2 つのサンプル コードです:
1. GIL を使用しない場合のコード サンプル:
from time import sleep,ctime import thread def loop0(): print 'start loop 0 at: ',ctime() sleep(4) print 'loop 0 done at: ',ctime() def loop1(): print 'start loop 1 at: ',ctime() sleep(2) print 'loop 1 done at: ',ctime() def main(): print 'start at: ',ctime() thread.start_new_thread(loop0,()) thread.start_new_thread(loop1,()) sleep(6) print 'all loop is done, ' ,ctime() if __name__=='__main__': main() 输出结果: start at: Thu Jan 28 10:46:27 2016 start loop 0 at: Thu Jan 28 10:46:27 2016 start loop 1 at: Thu Jan 28 10:46:27 2016 loop 1 done at: Thu Jan 28 10:46:29 2016 loop 0 done at: Thu Jan 28 10:46:31 2016 all loop is done, Thu Jan 28 10:46:33 2016
上記の出力からわかるように、2 つのスレッドが正常に開始されました。 2 秒でループ 1 が最初に完了し、4 秒でループ 0 が完了し、さらに 2 秒後にメイン スレッドが完了します。メインスレッド全体が 6 秒を経過し、loop0 とloop1 が同時に完了します。
2. GIL を使用したコード例:
import thread from time import sleep,ctime loops = [4,2] def loop(nloop,nsec,lock): print 'start loop',nloop,'at: ',ctime() sleep(nsec) print 'loop',nloop,'done at:',ctime() lock.release() def main(): print 'starting at:',ctime() locks = [] nloops = range(len(loops)) for i in nloops: lock = thread.allocate_lock() #创建锁的列表,存在locks中 lock.acquire() locks.append(lock) for i in nloops: thread.start_new_thread(loop,(i,loops[i],locks[i])) #创建线程,参数为循环号,睡眠时间,锁 for i in nloops: while locks[i].locked(): #等待循环完成,解锁 pass print 'all DONE at:',ctime() if __name__ == '__main__': main() 以上输出如下: starting at: Thu Jan 28 14:59:22 2016 start loop 0 at: Thu Jan 28 14:59:22 2016 start loop 1 at: Thu Jan 28 14:59:22 2016 loop 1 done at: Thu Jan 28 14:59:24 2016 loop 0 done at: Thu Jan 28 14:59:26 2016 all DONE at: Thu Jan 28 14:59:26 2016
は 4 秒続きます。これにより効率が向上し、メインスレッドで時間を計測するのに sleep() 関数を使用するよりも合理的です。
2. スレッドモジュール
1. Thread クラス
thread クラスでは、次の 3 つのメソッドを使用してスレッドを作成できます:
(1) スレッド インスタンスを作成し、それに関数を渡します
(2) ) スレッドインスタンスを作成し、呼び出し可能なクラスオブジェクトを渡します
(3) スレッドからサブクラスを派生し、このサブクラスのオブジェクトを作成します
方法(1)
__author__ = 'dell' import threading from time import sleep,ctime def loop0(): print 'start loop 0 at:',ctime() sleep(4) print 'loop 0 done at:',ctime() def loop1(): print 'start loop 1 at:',ctime() sleep(2) print 'loop 1 done at:',ctime() def main(): print 'starting at:',ctime() threads = [] t1 = threading.Thread(target=loop0,args=()) #创建线程 threads.append(t1) t2 = threading.Thread(target=loop1,args=()) threads.append(t2) for t in threads: t.setDaemon(True)<span style="white-space:pre"> </span> #开启守护线程(一定要在start()前调用) t.start()<span style="white-space:pre"> </span> #开始线程执行 for t in threads:<span style="white-space:pre"> </span> t.join()<span style="white-space:pre"> </span> #将程序挂起阻塞,直到线程结束,如果给出数值,则最多阻塞timeout秒 if __name__ == '__main__': main() print 'All DONE at:',ctime() 在这里,就不用像thread模块那样要管理那么多锁(分配、获取、释放、检查等)了,同时我也减少了循环的代码,直接自己编号循环了,得到输出如下: starting at: Thu Jan 28 16:38:14 2016 start loop 0 at: Thu Jan 28 16:38:14 2016 start loop 1 at: Thu Jan 28 16:38:14 2016 loop 1 done at: Thu Jan 28 16:38:16 2016 loop 0 done at: Thu Jan 28 16:38:18 2016 All DONE at: Thu Jan 28 16:38:18 2016
結果は同じですが、コードから論理的な観点から見ると、はるかに明確です。残りの2種類はここでは掲載しません。 Thread のインスタンス化と thread.start_new_thread の呼び出しの最大の違いは、新しいスレッドがすぐに実行を開始しないことです。つまり、スレッド モジュールの Thread クラスでは、インスタンス化した後、 を呼び出した後に統合されます。 start() 関数を実行します。これにより、プログラムは良好な同期特性を持ちます。
以下はシングルスレッドとマルチスレッドの比較例で、2組の演算がそれぞれ乗算と除算で完了し、マルチスレッド化による効率の向上を示しています
from time import ctime,sleep import threading def multi(): num1 = 1 print 'start mutiple at:',ctime() for i in range(1,10): num1 = i*num1 sleep(0.2) print 'mutiple finished at:',ctime() return num1 def divide(): num2 = 100 print 'start division at:',ctime() for i in range(1,10): num2 = num2/i sleep(0.4) print 'division finished at:',ctime() return num2 def main(): print '---->single Thread' x1 = multi() x2 = divide() print 'The sum is ',sum([x1,x2]),'\nfinished singe thread',ctime() print '----->Multi Thread' threads = [] t1 = threading.Thread(target=multi,args=()) threads.append(t1) t2 = threading.Thread(target=divide,args=()) threads.append(t2) for t in threads: t.setDaemon(True) t.start() for t in threads: t.join() if __name__ == '__main__': main() 结果如下: ---->single Thread start mutiple at: Thu Jan 28 21:41:18 2016 mutiple finished at: Thu Jan 28 21:41:20 2016 start division at: Thu Jan 28 21:41:20 2016 division finished at: Thu Jan 28 21:41:24 2016 The sum is 362880 finished singe thread Thu Jan 28 21:41:24 2016 ----->Multi Thread start mutiple at: Thu Jan 28 21:41:24 2016 start division at: Thu Jan 28 21:41:24 2016 mutiple finished at: Thu Jan 28 21:41:26 2016 division finished at: Thu Jan 28 21:41:27 2016 The sum is : 362880