Python で共有メモリの問題を修正し、共有リソースをロックする
このチュートリアルでは、マルチプロセッシング共有メモリのさまざまな側面について説明し、共有メモリを使用して問題を修正する方法を示します。 また、lock
を使用して Python で共有リソースをロックする方法も学びます。
multiprocessing.Array()
を使用して Python で共有メモリを使用する
マルチプロセッシング
の最も重要な側面の 1つは、複数の子プロセスがある場合にプロセス間でデータを共有することです。
processing
モジュールの機能を使用して作成する子プロセスの重要な特性の 1つは、それらが独立して実行され、独自のメモリ空間を持つことです。
これは、子のプロセスにいくらかのメモリ空間があることを意味します。 また、変数は、その親のメモリ空間ではなく、独自のメモリ空間で作成しようとするか、変更されます。
例を取り上げ、multiprocessing
モジュールをインポートしてコードに飛び込んで、この概念を理解してみましょう。
RESULT
と呼ばれる空のリストを作成し、Make_Sqaured_List()
と呼ばれる関数を定義しました。これは、指定されたリストの要素を 2 乗してグローバルな RESULT
リストに追加します。
Procc_1
オブジェクトは Process()
クラスと同等であり、ターゲットを括弧なしの Make_Sqaured_List
関数として設定します。
そして、args
パラメータには、Items_list
という別のリストを渡します。このリストは、Make_Sqaured_List()
関数への引数として与えられます。
コード例:
import multiprocessing
RESULT = []
def Make_Sqaured_List(Num_List):
global RESULT
for n in Num_List:
RESULT.append(n ** 2)
print(f"Result: {RESULT}")
if __name__ == "__main__":
Items_list = [5, 6, 7, 8]
Procc_1 = multiprocessing.Process(target=Make_Sqaured_List, args=(Items_list,))
Procc_1.start()
Procc_1.join()
この子プロセスを実行してみましょう。グローバル リストの値である子プロセスに従って結果を取得します。
Result: [25, 36, 49, 64]
しかし、まだ空である RESULT
リストを印刷しようとすると、RESULT
リストで何が起こっているのでしょうか?
print(RESULT)
出力:
[]
メイン プロセスによると、親プロセスはまだ空ですが、子プロセスによると、RESULT
リストにはいくつかのコンテンツがあります。 これは単に、異なるプロセスの両方が異なるメモリ空間を持っていることを意味します。
これは、最初に空の RESULT
リストを持つメイン プログラムであるプロセス 1 があるシナリオで理解できます。
また、子プロセスを作成すると、最初は空で、その後 Make_Sqaured_List()
関数が実行されるため、RESULT
リストにはいくつかのアイテムが含まれます。 ただし、このメモリ空間の親プロセスから RESULT
にアクセスしているため、変更は表示されません。
コード例:
import multiprocessing
RESULT = []
def Make_Sqaured_List(Num_List):
global RESULT
for n in Num_List:
RESULT.append(n ** 2)
print(f"Result: {RESULT}")
if __name__ == "__main__":
Items_list = [5, 6, 7, 8]
Procc_1 = multiprocessing.Process(target=Make_Sqaured_List, args=(Items_list,))
Procc_1.start()
Procc_1.join()
print(RESULT)
出力:
Result: [25, 36, 49, 64]
[]
それで、これに対する解決策は何ですか? しかし、まず、それを解決する方法を見てみましょう。
マルチプロセッシング間でデータを共有する際の問題を解決するソリューション
このセクションでは、変更の値を取得し、マルチプロセッシング間でデータを共有する際の問題を修正するのに役立つソリューションについて説明します。
解決策は共有メモリと呼ばれます。 multiprocessing
モジュールは、プロセス間でデータを共有するために使用できる Array
と Value
と呼ばれる 2 種類のオブジェクトを提供します。
Array
は共有メモリから割り当てられた配列です。 基本的に、コンピューターのメモリには、共有メモリと呼ばれる部分、または複数のプロセスがアクセスできる領域があります。
その共有メモリで、新しい配列または新しい値を作成します。 これらの追加された値は、基本的な Python データ構造ではありません。 multiprocessing
モジュール自体で定義されている別のものがあります。
ここで、multiprocessing.Array()
を使用して RESULT_ARRAY
というオブジェクトを宣言します。 次に、この配列で、データ型を渡す必要があります。 i
を文字列として渡します。つまり、その中に整数値を入れ、size
を指定する必要があります。
コード例:
RESULT_ARRAY = multiprocessing.Array("i", 4)
サイズを同時に指定できるように、C プログラミング スタイルの配列に関連しています。 このようにして、目的の場所にオブジェクトを格納できます。
OBJ_Sum
は multiprocessing.Value()
に等しい新しい値を作成し、値を保存して入力します。
コード例:
OBJ_Sum = multiprocessing.Value("i")
次に、関数を呼び出す multiprocessing.Process()
に等しい procc_1
というオブジェクトを作成します。 Make_Sqaured_List()
という関数を作成しました。これは 3つの引数を取ります。
- リスト
- 配列オブジェクト
- 値オブジェクト。
args
と呼ばれるこの Process
引数を使用して、これら 3つの引数を関数に渡します。 たとえば、次のコード フェンスを見てください。
コード例:
Procc_1 = multiprocessing.Process(
target=Make_Sqaured_List, args=(Items_list, RESULT_ARRAY, OBJ_Sum)
)
Make_Sqaured_List()
関数では、enumerate()
関数を使用して Items_list
を反復します。 Items_list
の index
と value
を取得できるようにします。
これは C スタイルの配列であるため、インデックスを使用して値を配列に割り当てる必要があります。 また、配列の値を合計します。OBJ_Sum.value
は multiprocessing.Value()
のプロパティです。
コード例:
def Make_Sqaured_List(Items_list, RESULT, OBJ_Sum):
for i, n in enumerate(Items_list):
RESULT[i] = n ** 2
OBJ_Sum.value = sum(RESULT)
メインプロセスでいくつかの変数を定義し、子プロセスによって呼び出される関数を変更しました。 したがって、私たちの主な議題は、これらの変更された値をメイン プロセスで取得できるかどうかです。
これで、子プロセスに反映された配列にアクセスし、OBJ_Sum.value
を使用してその合計を取得できます。 たとえば、次のコード スニペットを参照してください。
コード例:
import multiprocessing
def Make_Sqaured_List(Items_list, RESULT, OBJ_Sum):
for i, n in enumerate(Items_list):
RESULT[i] = n ** 2
OBJ_Sum.value = sum(RESULT)
if __name__ == "__main__":
Items_list = [5, 6, 7, 8]
RESULT_ARRAY = multiprocessing.Array("i", 4)
OBJ_Sum = multiprocessing.Value("i")
Procc_1 = multiprocessing.Process(
target=Make_Sqaured_List, args=(Items_list, RESULT_ARRAY, OBJ_Sum)
)
Procc_1.start()
Procc_1.join()
print(RESULT_ARRAY[:])
print(OBJ_Sum.value)
出力:
[25, 36, 49, 64]
174
このようにして、親プロセスで定義されたオブジェクトに変更を加えることができ、子プロセスから返される変更を行うことができます。 これは、共有メモリ技術を使用することで可能になります。
multiprocessing.Lock()
を使用して Python で共有リソースをロックする
ロック
と呼ばれる 1つの重要なトピックについて説明します。 コンピュータ サイエンスやオペレーティング システムのクラスを受講したことがある場合は、すでに ロック
について学習しています。 ただし、マルチプロセッシング
およびオペレーティング システムの概念に関しては、ロックは重要な概念です。
まず、実生活で ロック
が必要な理由を考えてみましょう。 私たちの日常生活では、2 人が同時にアクセスできないリソースもあります。
たとえば、バスルームのドアにはロック
が付いています。これは、2 人が同時にアクセスしようとすると、かなり恥ずかしい状況が発生するためです。 だからこそ、共有リソースであるバスルームをロック
で保護しています。
同様に、プログラミングの世界では、2つのプロセスまたはスレッドが共有メモリ ファイルやデータベースなどの共有リソースにアクセスしようとするたびに発生します。 問題が発生する可能性があるため、そのアクセスをロック
で保護する必要があります。
その保護をプログラムに追加しないとどうなるかは、例を実行することでわかります。 繰り返しますが、これはバンキング ソフトウェア プログラムで、ここには 2つのプロセスがあります。
最初のプロセスは MONEY_DP()
関数を使用して銀行にお金を入金することであり、2つ目は MONEY_WD()
関数を使用して銀行からお金を引き出すことです。 そして最後に、最終的なバランスを印刷しています。
MONEY_DP
セクションの 200$
ドル残高から始めます。 100$
を入金しています。for
ループがあり、100
回トレースし、反復ごとに 01$
が銀行口座に追加されます。
同様に、MONEY_WD
関数では、同じループが 100
回繰り返され、そのたびに銀行口座から 1
ドルが差し引かれます。
コード例:
import multiprocessing
import time
def MONEY_DP(B):
for i in range(100):
time.sleep(0.01)
B.value = B.value + 1
def MONEY_WD(B):
for i in range(100):
time.sleep(0.01)
B.value = B.value - 1
ここで、前のセクションで既に学習した value
という共有メモリ変数を使用しています。 この multiprocessing
値は共有メモリ リソースなので、このプログラムを実行しようとするとどうなるか見てみましょう。
if __name__ == "__main__":
B = multiprocessing.Value("i", 200)
Deposit = multiprocessing.Process(target=MONEY_DP, args=(B,))
Withdrawl = multiprocessing.Process(target=MONEY_WD, args=(B,))
Deposit.start()
Withdrawl.start()
Deposit.join()
Withdrawl.join()
print(B.value)
これを複数回実行すると、毎回異なる値が出力されますが、200$
が出力されるはずです。
出力:
# execution 1
205
# execution 2
201
# execution 3
193
なぜこれが起こるのですか? これは主に、このプロセスが共有メモリ内の B.value
という変数を読み取ろうとしたときに発生します。
B.value
が 200$
の値を持っているとしましょう。それを読み取り、1つ追加し、同じ値を同じ変数に戻します。
B.value
は 200$
であり、オペレーティング システム レベルでこの加算操作を行っているため、オペレーティング システム レベルでは、複数のアセンブリ ライン命令が実行されます。
これで、変数 200
を読み取りました。 1 が追加され、201
が B.value
変数に割り当てられます。
コード例:
B.value = B.value + 1
同時に実行している間に、この命令も MONEY_WD()
関数で実行されました。
コード例:
B.value = B.value - 1
最初に入金してから引き出していますが、プロセスが B.value
を読み取ろうとすると、入金プロセスが元の変数に書き戻していないため、まだ 200
になります。
MONEY_WD
プロセス内で B.value
を 201
として読み取る代わりに、B.value
を 200
として読み取り、1
を減少させた後、199
になります。
そのため、一貫性のない動作が発生しています。 まず、Lock
を使用してアクセスをロックしましょう。 ここで、lock
という変数を作成し、multiprocessing
モジュールを使用して Lock
クラスを使用します。
コード例:
lock = multiprocessing.Lock()
次に、その lock
を両方のプロセスに渡し、両方のプロセス内で lock.acquire()
を呼び出して lock
を置き、次に lock
を解放するために lock.release()
関数。
これらの lock
関数は、クリティカル セクションと呼ばれる共有リソースにアクセスしている間、コード セクションを保護します。
コード例:
import multiprocessing
import time
def MONEY_DP(B, lock):
for i in range(100):
time.sleep(0.01)
lock.acquire()
B.value = B.value + 1
lock.release()
def MONEY_WD(B, lock):
for i in range(100):
time.sleep(0.01)
lock.acquire()
B.value = B.value - 1
lock.release()
if __name__ == "__main__":
B = multiprocessing.Value("i", 200)
lock = multiprocessing.Lock()
Deposit = multiprocessing.Process(target=MONEY_DP, args=(B, lock))
Withdrawl = multiprocessing.Process(target=MONEY_WD, args=(B, lock))
Deposit.start()
Withdrawl.start()
Deposit.join()
Withdrawl.join()
print(B.value)
現在、このコードは毎回 200
を出力しています。
出力:
200
PS C:\Users\Dell\Desktop\demo> python -u "c:\Users\Dell\Desktop\demo\demo.py"
200
PS C:\Users\Dell\Desktop\demo> python -u "c:\Users\Dell\Desktop\demo\demo.py"
200
Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.
LinkedIn関連記事 - Python Error
- AttributeError の解決: 'list' オブジェクト属性 'append' は読み取り専用です
- AttributeError の解決: Python で 'Nonetype' オブジェクトに属性 'Group' がありません
- AttributeError: 'generator' オブジェクトに Python の 'next' 属性がありません
- AttributeError: 'numpy.ndarray' オブジェクトに Python の 'Append' 属性がありません
- AttributeError: Int オブジェクトに属性がありません
- AttributeError: Python で 'Dict' オブジェクトに属性 'Append' がありません