Python で共有メモリの問題を修正し、共有リソースをロックする

Salman Mehmood 2023年6月21日
  1. multiprocessing.Array() を使用して Python で共有メモリを使用する
  2. multiprocessing.Lock() を使用して Python で共有リソースをロックする
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 モジュールは、プロセス間でデータを共有するために使用できる ArrayValue と呼ばれる 2 種類のオブジェクトを提供します。

Array は共有メモリから割り当てられた配列です。 基本的に、コンピューターのメモリには、共有メモリと呼ばれる部分、または複数のプロセスがアクセスできる領域があります。

その共有メモリで、新しい配列または新しい値を作成します。 これらの追加された値は、基本的な Python データ構造ではありません。 multiprocessing モジュール自体で定義されている別のものがあります。

ここで、multiprocessing.Array() を使用して RESULT_ARRAY というオブジェクトを宣言します。 次に、この配列で、データ型を渡す必要があります。 i を文字列として渡します。つまり、その中に整数値を入れ、size を指定する必要があります。

コード例:

RESULT_ARRAY = multiprocessing.Array("i", 4)

サイズを同時に指定できるように、C プログラミング スタイルの配列に関連しています。 このようにして、目的の場所にオブジェクトを格納できます。

OBJ_Summultiprocessing.Value() に等しい新しい値を作成し、値を保存して入力します。

コード例:

OBJ_Sum = multiprocessing.Value("i")

次に、関数を呼び出す multiprocessing.Process() に等しい procc_1 というオブジェクトを作成します。 Make_Sqaured_List() という関数を作成しました。これは 3つの引数を取ります。

  1. リスト
  2. 配列オブジェクト
  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_listindexvalue を取得できるようにします。

これは C スタイルの配列であるため、インデックスを使用して値を配列に割り当てる必要があります。 また、配列の値を合計します。OBJ_Sum.valuemultiprocessing.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.value200$ の値を持っているとしましょう。それを読み取り、1つ追加し、同じ値を同じ変数に戻します。

B.value200$ であり、オペレーティング システム レベルでこの加算操作を行っているため、オペレーティング システム レベルでは、複数のアセンブリ ライン命令が実行されます。

これで、変数 200 を読み取りました。 1 が追加され、201B.value 変数に割り当てられます。

コード例:

B.value = B.value + 1

同時に実行している間に、この命令も MONEY_WD() 関数で実行されました。

コード例:

B.value = B.value - 1

最初に入金してから引き出していますが、プロセスが B.value を読み取ろうとすると、入金プロセスが元の変数に書き戻していないため、まだ 200 になります。

MONEY_WD プロセス内で B.value201 として読み取る代わりに、B.value200 として読み取り、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
著者: Salman Mehmood
Salman Mehmood avatar Salman Mehmood avatar

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