このチュートリアルでは、Python の重要なトピックの 1 つである GIL に焦点を当てます。また、GIL がコード実装で Python プログラムのパフォーマンスにどのような影響を与えるかについても説明します。このトピックに入る前に、GIL の基本的な概念を理解しましょう。
GIL またはグローバル インタプリタ ロック
Python グローバル インタープリター ロック (GIL) は、マルチスレッド プログラミングの重要な部分です。これは、複数のプロセスを操作するときに使用されるプロセス ロックの一種です。 1 つのスレッドのみに制御を与えます。通常、Python は単一のプロセスを実行するために単一のスレッドを使用します。 GIL を使用すると、シングルスレッド プロセスとマルチスレッド プロセスで同じパフォーマンス結果が得られます。スレッドが妨げられ、単一のスレッドとして動作するため、Python でのマルチスレッドの実現が制限されます。
注 - スレッド化パッケージでは複数の CPU コアを使用できないため、Python はマルチスレッド化をサポートしていません。
Python 開発者が GIL を使用する理由
Python は、メモリ管理に使用される独自の参照カウンター機能を提供します。参照カウンターは、データ オブジェクトに値を割り当てるために Python で内部的に行われる参照の合計数をカウントします。参照カウントがゼロに達すると、オブジェクトに割り当てられたメモリが解放されます。以下の例を見てみましょう。
例 -
import sys a = [] b = a sys.getrefcount(a)
参照カウント変数に関する主な懸念は、2 つまたは 3 つのスレッドがその値を同時に増加または減少させようとすると影響を受ける可能性があることです。これは競合状態として知られています。この状態が発生すると、解放されないメモリ リークが発生する可能性があります。 Python プログラムがクラッシュしたりバグったりする可能性があります。
GIL は、スレッド間で共有されるすべてのデータ構造に対するロックを使用して、データ構造が一貫性なく変更されないようにすることで、このような状況を取り除くのに役立ちます。 Python は、スレッドセーフなメモリ管理を扱うため、GIL を実装する簡単な方法を提供します。 GIL では、Python で処理するためにスレッドに単一のロックを提供する必要があります。処理する必要があるロックは 1 つだけなので、シングルスレッド プログラムのパフォーマンスが向上します。また、CPU に依存するプログラムの作成にも役立ち、デッドロック状態を防ぎます。
マルチスレッド Python プログラムへの影響
パフォーマンスの CPU 限界と、一般的な Python プログラムまたはコンピューター プログラムの I/O 限界の間には違いがあります。 CPU に依存するプログラムは通常、CPU を限界まで押し上げます。これらのプログラムは一般に、行列の乗算、焼き付け、画像処理などの数学的計算に使用されます。
I/O バウンド プログラムは、ユーザー、ファイル、データベース、ネットワークなどによって生成される入出力を取得するために時間を費やすプログラムです。このようなプログラムは、ソースが入力を提供するまで、かなりの時間待機する必要があります。一方、ソースにも独自の処理時間があります。たとえば、ユーザーは入力として何を入力するかを考えています。
次の例を理解してみましょう。
例 -
import time from threading import Thread COUNT = 100000000 def countdown(num): while num>0: num -= 1 start_time = time.time() countdown(COUNT) end_time = time.time() print('Time taken in seconds -', end_time - start_time)
出力:
Time taken in seconds - 7.422671556472778
次に、2 つのスレッドを実行して上記のコードを変更します。
例 - 2:
import time from threading import Thread COUNT = 100000000 def countdown(num): while num>0: num -= 1 thread1 = Thread(target=countdown, args=(COUNT//2,)) thread2 = Thread(target=countdown, args=(COUNT//2,)) start_time = time.time() thread1.start() thread2.start() thread1.join() thread2.join() end_time = time.time() print('Time taken in seconds -', end_time - start_time)
出力:
Time taken in seconds - 6.90830135345459
見てわかるように、両方のコードが完了するまでに同じ時間がかかりました。 GIL は、CPU バウンドのスレッドが 2 番目のコードで並列実行されるのを防ぎました。
GIL がまだ削除されていないのはなぜですか?
多くのプログラマーがこれに関して不満を抱いていますが、Python では GIL の削除ほど重要な変更をもたらすことはできません。もう一つの理由は、現時点でGILが改善されていないことです。 Python 3 で変更すると、重大な問題が発生します。 GIL を削除する代わりに、GIL の概念を改善できます。グイド・ヴァン・ロッサム氏によると、
「シングルスレッド プログラム (およびマルチスレッドだが I/O バウンド プログラム) のパフォーマンスが低下しない場合にのみ、Py3k へのパッチ セットを歓迎します。」
GIL で解決されるのと同じ問題を解決する方法も多数ありますが、実装するのは困難です。
Python の GIL に対処する方法
プログラムの GIL を防ぐには、マルチプロセッシングの使用が最適な方法です。 Python では、実行するプロセスごとにさまざまなインタープリターが提供されるため、このシナリオでは、マルチプロセスの各プロセスに単一のスレッドが提供されます。次の例を理解してみましょう。
例 -
Javaのif elseループ
from multiprocessing import Pool import time COUNT = 50000000 def countdown(num): while num>0: num -= 1 if __name__ == '__main__': pool = Pool(processes=2) start_time = time.time() r1 = pool.apply_async(countdown, [COUNT//2]) r2 = pool.apply_async(countdown, [COUNT//2]) pool.close() pool.join() end_time = time.time() print('Time taken in seconds -', end_time - start_time)
出力:
Time taken in seconds - 3.3707828521728516
それなりのパフォーマンスが向上しているように見えるかもしれませんが、プロセス管理には独自のオーバーヘッドがあり、複数のプロセスは複数のスレッドよりも重くなります。
結論
このチュートリアルでは、GIL とその使用方法について説明しました。同時に実行する単一のスレッドに制御を与えます。このチュートリアルでは、Python プログラマーにとって GIL が重要な理由についても説明しました。