マルチプロセッシング(マルチコアコンピューティング)

マルチコアコンピューティング

目次

1.マルチプロセッシングとは

2.プロセスの追加

3.ストレージプロセス出力キュー

4.効率比較スレッディングとマルチプロセッシング

5.プロセスプールプール

6.共有メモリ

7.プロセスロックロック

1.マルチプロセッシングとは

計算のためにタスクを複数のコアに割り当てます。単一のコアには独自の計算スペースと計算能力があります。タスクの各部分が同時に実行されるため、マルチスレッドの疑似並列ではなく並列操作が可能になります。マルチコアコンピュータその真の可能性を実現する

マルチプロセッシングはマルチスレッドに似ています。どちらもPythonの並列操作に使用されますが、スレッド化されているのに、なぜPythonにマルチプロセッシングがあるのですか?理由は単純で、GILなどのスレッドの欠点のいくつかを補うために使用されます。スレッドチュートリアルで説明されてい
ます。マルチプロセッシングの使用も非常に簡単です。スレッドについてある程度理解していれば、楽しむ時間は長くなります。Pythonはマルチプロセッシングとスレッドをほぼ同じように使用するため、これで簡単に始めることができます。コンピューターのマルチコアシステムのパワーを使いやすくします!

2.プロセスの追加

#导入线程进程标准模块 
import multiprocessing as mp
import threading as td

#定义一个被线程和进程调用的函数 
def job(a,d):
    print('aaaaa')

#创建线程和进程,只是定义线程或进程要做什么,传入的参数有什么,名字叫什么,但是还未开始工作。
t1 = td.Thread(target=job,args=(1,2))
p1 = mp.Process(target=job,args=(1,2))

#分别启动线程和进程,线程与进程开始工作
t1.start()
p1.start()

#分别连接线程和进程,线程和进程join作用一致
t1.join()
p1.join()

注:スレッドとプロセスの最初の文字は大文字にする必要があります。呼び出された関数には括弧がありません。括弧がある場合、関数はプロセスまたはスレッドを増やすことなく直接呼び出されます。呼び出された関数のパラメーターはargs(。 ..)。上記の比較コードからわかるように、スレッドとプロセスの使用は似ています

マルチプロセスアプリケーション

完全なアプリケーションコード:

import multiprocessing as mp

def job(a,d):
    print('aaaaa')

if __name__=='__main__':
    p1 = mp.Process(target=job,args=(1,2))
    p1.start()
    p1.join()

マルチプロセスマルチプロセスを使用する場合は、main関数で使用する必要があり、直接実行できないため、エラーが発生します。これは特別なフォーマット要件です。Macの動作環境はターミナル環境である必要があります。実行の終了後に他の編集ツールが結果を印刷しないように見える場合があります。WindowsおよびLinuxで直接実行しても問題ありません。実行後に印刷された結果ターミナルには次のようなものがあります。

aaaaa

3.ストレージプロセス出力キュー

Queueの機能は、各コアまたはスレッドの計算結果をキューに入れ、各スレッドまたはコアの実行後にキューから結果を取得して、計算をロードし続けることです。理由は単純です。複数のスレッドによって呼び出される関数は戻り値を持つことができないため、Queueは複数のスレッドの結果を格納するために使用されます。

結果をキューに入れて、複数のスレッドによって呼び出される関数を定義します。qはキューのようなもので、各関数の実行結果を保存するために使用されます。

#该函数没有返回值!!!
def job(q):
    res=0
    for i in range(1000):
        res+=i+i**2+i**3
    q.put(res)    #queue
#主函数 定义一个多线程队列,用来存储结果
if __name__=='__main__':
    q = mp.Queue()
#定义两个线程函数,用来处理同一个任务, args的参数只要一个值的时候,参数后面需要加一个逗号,表示args是可迭代的,后面可能还有别的参数,不加逗号会出错

p1 = mp.Process(target=job,args=(q,))
p2 = mp.Process(target=job,args=(q,))
#分别启动、连接两个线程
p1.start()
p2.start()
p1.join()
p2.join()
#上面是分两批处理的,所以这里分两批输出,将结果分别保存

res1 = q.get()
res2 = q.get()
#打印最后的运算结果
print(res1+res2)

実行中は、ターミナルにいる必要があり、最終結果は次のようになります。

499667166000

総括する

最初にマルチスレッドキューを定義し、このキューをマルチプロセスパラメータに配置します。プロセスを実行した後、結果を結果キューに配置します。すべてのプロセスが終了すると、結果キューは値でいっぱいになります。実行中のすべてのプロセスによって取得され、キュー内の値を1つずつ読み取り、各プロセスによって取得された結果を取得できます。次に、取得された結果に対してさらに計算を行うことができます。これが出力結果の合計です。

4.効率比較スレッディングとマルチプロセッシング

同じタスクが複数のプロセスで高速であるか、複数のスレッドで高速であるか、まったく高速でないかを比較してみましょう。

マルチプロセッシングを作成する

前のセクションと同様に、最初にマルチプロセッシングをインポートし、実装するjob()を定義します。同時に、簡単に比較できるように、計算数を1000000に増やします。

import multiprocessing as mp

def job(q):
    res = 0
    for i in range(1000000):
        res += i + i**2 + i**3
    q.put(res) # queue
    
#因为多进程是多核运算,所以我们将上节的多进程代码命名为multicore()
def multicore():
    q = mp.Queue()
    p1 = mp.Process(target=job, args=(q,))
    p2 = mp.Process(target=job, args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    res1 = q.get()
    res2 = q.get()
    print('multicore:',res1 + res2)

マルチスレッドを作成する

次に、マルチスレッドプログラムを作成します。マルチスレッドの作成とマルチプロセッシングには多くの類似点があります。最初にスレッドをインポートし、次にmultithread()を定義して同じタスクを実行します

import threading as td

def multithread():
    q = mp.Queue() # thread可放入process同样的queue中
    t1 = td.Thread(target=job, args=(q,))
    t2 = td.Thread(target=job, args=(q,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    res1 = q.get()
    res2 = q.get()
    print('multithread:', res1 + res2)

通常の関数を作成する

最後に、最も一般的な関数を定義します。上記の例では、2つのプロセスまたはスレッドを作成しました。どちらもjob()で2つの操作を実行したため、normal()でも2回ループさせます。

def normal():
    res = 0
    for _ in range(2):
        for i in range(1000000):
            res += i + i**2 + i**3
    print('normal:', res)

営業時間

最後に、各関数の実行時間を比較するために、時間をインポートしてから、定義された関数を順番に実行する必要があります。

import time

if __name__ == '__main__':
    st = time.time()
    normal()
    st1 = time.time()
    print('normal time:', st1 - st)
    multithread()
    st2 = time.time()
    print('multithread time:', st2 - st1)
    multicore()
    print('multicore time:', time.time() - st2)

これで、実際の操作の比較を見てみましょう。
比較結果:

# range(1000000)
('normal:', 499999666667166666000000L)
('normal time:', 1.1306169033050537)
('thread:', 499999666667166666000000L)
('multithread time:', 1.3054230213165283)
('multicore:', 499999666667166666000000L)
('multicore time:', 0.646507978439331)

結果分析:

通常/マルチスレッド/マルチプロセスの実行時間は、それぞれ1.13秒、1.3秒、0.64秒です。マルチコア/マルチプロセスが最速であり、複数のタスクが同時に実行されていることを示しています。マルチスレッドの実行時間は、実際には何もしないプログラムの実行時間よりも遅く、マルチスレッドにはまだ特定の欠点があることを示しています。

操作の数を10倍にしてから、3つのメソッドの実行時間を見てみましょう。

「」

範囲(10000000)

( 'normal:'、4999999666666716666660000000L)
( 'normal time:'、40.041773080825806)
( 'thread:'、4999999666666716666660000000L)
( 'multithread time:'、41.777158975601196)
( 'multicore:'、4999999666666716666660000000L)
( 'multicore time:'、22.4337899906 )
"" "
今回も実行時間はマルチプロセス<通常<マルチスレッドであるため、どちらの方法がより効率的であるかが明確にわかります。

5.プロセスプールプール

今回は、プロセスプールプールについて説明します。プロセスプールは、実行してプールに配置したいものです。Pythonは、プロセスのタスクの割り当て方法、結果の処理方法など、マルチプロセスの問題を単独で解決します。

首先import multiprocessing和定义job()

import multiprocessing as mp

def job(x):
    return x*x
#进程池 Pool() 和 map() 
然后我们定义一个Pool
def multicore():
    pool = mp.Pool()
    res = pool.map(job, range(10))
    print(res)
    
if __name__ == '__main__':
    multicore()

プロセスプールを使用すると、値を必要な関数に一致させ、データをその関数にスローできます。その後、プロセス実行関数によって返された値が返されます。前に述べたプロセスには、tdがあります。呼び出しを実行すると、戻り値はありません。彼の呼び出し関数の出力結果をキューに入れて、キューから結果値を返すことしかできませんが、プールでは、作成したものがマルチによって呼び出されます。 -process関数には戻り値があり、プロセス結果の戻り値としてresに直接返されます。

そして、プロセスプールに指定した関数を実行させるにはどうすればよいですか?呼び出す関数ジョブと指定したパラメーターマップを一緒にマップする必要があります。次に方程式ジョブをマップし、次に0から9の値を指定します。リストの。

プールを作成したら、プールを特定の関数に対応させ、データをプールにスローすると、プールは関数によって返された値を返します。プールと前のプロセスの違いは、プールにスローされた関数には戻り値があり、プロセスには戻り値がないことです。

次に、map()を使用して結果を取得します。関数と反復操作の値をmap()に入れる必要があります。そうすると、CPUコアに自動的に割り当てられ、結果が返されます。
実行してみましょうそれ。

実行結果:
python [0、1、4、9、16、25、36、49、64、81]

運用分析:プロセスプールを定義し、方程式と計算する値をその中に入れます。プロセスプールは、各コア、各CPU、計算する各プロセスにタスクを自動的に割り当て、計算結果はプロセス終了と見なされます。戻り値が返されます。

カスタムコア数量

プールが実際に複数のコアを使用しているかどうかをどのように知ることができますか?反復回数を増やしてから、CPU負荷をオンにして、CPUの動作を確認できます。

CPU負荷を開く(Mac):アクティビティモニター> CPU> CPU負荷(ワンクリック)

デフォルトのプールサイズはCPUコアの数です。つまり、タスクはすべてのコアに割り当てられます。プールのprocessesパラメーターを渡すことで、必要なコアの数をカスタマイズすることもできます。

def multicore():
    pool = mp.Pool(processes=3) # 定义CPU核数量为3
    res = pool.map(job, range(10))
    print(res)
apply_async() 

map()に加えて、Poolには結果を返す方法もあります。つまり、関数と値を組み合わせたapply_async()です。Apply_async()は1つの値のみを渡すことができ、操作のために1つのコアにのみ配置されます。ただし、値を渡すときはiterableに注意してください。そのため、入力値の後にコンマを追加する必要があり、戻り値を取得するにはget()メソッドを使用する必要があります。

def multicore():
    pool = mp.Pool() 
    res = pool.map(job, range(10))
    print(res)
    res = pool.apply_async(job, (2,))
    # 用get获得结果
    print(res.get())

演算結果;

[0、1、4、9、16、25、36、49、64、81]
4

複数の値と組み合わせたapply_async()

apply_async()は単一の値とのみ組み合わせることができます。上記のマップのように複数の値と組み合わせる場合は、エラーが生成されます。

res = pool.apply_async(job, (2,3,4))

結果はエラーになります
。TypeError:job()は正確に1つの引数(3つ指定)
を取ります。つまり、apply_async()は1セットのパラメーターしか入力できません。

apply_async()を使用して複数の結果を出力する次にapply_async()を使用して
複数の反復を出力する方法ここでは、apply_async()をイテレーターに配置し、新しいmulti_resを定義します。

multi_res = [pool.apply_async(job, (i,)) for i in range(10)]

また、値を取り出す際には、1つずつ取り出す必要があります。

print([res.get() for res in multi_res])

コードを組み合わせる:

def multicore():
    pool = mp.Pool() 
    res = pool.map(job, range(10))
    print(res)
    res = pool.apply_async(job, (2,))
    # 用get获得结果
    print(res.get())
    # 迭代器,i=0时apply一次,i=1时apply一次等等
    multi_res = [pool.apply_async(job, (i,)) for i in range(10)]
    # 从迭代器中取出
    print([res.get() for res in multi_res])

運転結果

[ 0、1、4、9、16、25、36、49、64、81] #map ()
4
[0、1、4、9、16、25、36、49、64、81] #multi_res
can適用にイテレータを使用して得られた結果は、マップを使用して得られた結果と同じであることがわかります。

総括する

Poolのデフォルトの呼び出しはCPUコアの数です。プロセスパラメーターを
map()に渡すことで、CPUコアの数をカスタマイズできます。多くの反復パラメーターを入力し、複数のプロセスに自動的に割り当てます。複数のコアが操作を実行し、複数を返します。結果。

apply_async()はパラメーターのセットのみを配置でき、このパラメーターのセットのみを操作のためにコアに配置し、結果を返します。map()の効果を取得する場合は、反復する必要があります。

6.共有メモリ

[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-eKppRrOo-1614353754983)(3626EFE4BC544B46BB9F55F5A9A4B60A)]この図
は、一般的な4とCPUを示しています。そして、その共有メモリは彼の真ん中のボックス1の位置です。たとえば、通常の操作とマルチスレッド操作では、グローバル変数を定義すると、異なるスレッド間のグローバル変数は相互運用可能であり、相互に共有できます。マルチプロセスで異なっていてもグローバル変数Aがプロセス内の各cpuに渡された場合、異なるプロセス間で計算されたAが通信したい場合は機能しません。現時点では、グローバル変数を共有することはできません。たとえば、グローバル変数A 0の初期値は、最初のコア計算に1を追加し、2番目のコア計算に2を追加してから、次のコアに渡します。これは機能しません。CPU間で通信するため。コアの場合、共有メモリ方式を使用する必要があります。このセクションでは、異なるプロセス間の通信を実現するために共有メモリを定義する方法を学習します。共有メモリは複数のコアの中間にあります。各コアは共有メモリのコンテンツを取り込んで処理できます。最初に処理した後、次のコアにデータを処理させて、同じデータに対して複数のチェックを実行します。、情報共有を実現します。

共有価値

値データを使用して、共有メモリテーブルに保存できます。

import multiprocessing as mp

value1 = mp.Value('i', 0) 
value2 = mp.Value('d', 3.14)

dおよびiパラメーターはデータ型を設定するために使用され、dは倍精度浮動小数点型を表し、iは符号付き整数を表します。その他のフォームについては、このページの最後にある表を参照してください。

共有アレイ

Pythonのマルチプロセッシングには、共有メモリと対話してプロセス間でデータを共有できるArrayクラスもあります。

array = mp.Array('i', [1, 2, 3, 4])

ここでの配列はnumpyの配列とは異なり、多次元ではなく1次元のみにすることができます。値と同じように、データ形式を定義する必要があります。定義しないと、エラーが報告されます。次のセクションでは、これら2つの方法の使用法について説明します。

間違った形式

array = mp.Array('i', [[1, 2], [3, 4]]) # 2维list

演算結果:
"" "
TypeError:整数が必要です
" ""

参照データ形式

各パラメータで表されるデータ型

タイプコード Cタイプ Pythonタイプ バイト単位の最小サイズ
'b' 符号付き文字 int 1
'B' unsigned char int 1
'u' Py_UNICODE Unicode文字 2
'h' 短い署名 int 2
'H' unsigned short int 2
'i' 符号付き整数 int 2
'I' unsigned int int 2
'l' 長く署名した int 4
'L' unsigned long int 4
'q' 長い間署名 int 8
'Q' unsigned long long int 8
'f' 浮く 浮く 4
'd' ダブル 浮く 8

(出典:https://docs.python.org/3/library/array.html)

7.プロセスロックロック

このセクションでは、例として共有メモリでのロックの適用について説明します。

プロセスロックなし

プロセスロックを追加しないとどうなるか見てみましょう。

import multiprocessing as mp
import time

def job(v, num):
    for _ in range(5):
       #暂停0.1秒,让输出效果更明显
        time.sleep(0.1) 
        # v.value获取共享变量值
        v.value += num 
        print(v.value, end="")
        
def multicore():
    # 定义共享变量
    v = mp.Value('i', 0) 
    p1 = mp.Process(target=job, args=(v,1))
    p2 = mp.Process(target=job, args=(v,3)) # #设定不同的number看不同的进程如何如何抢夺内存
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    
if __name__ == '__main__':
    multicore()

上記のコードでは、両方のプロセスで操作できる共有変数vを定義しています。job()では、vに0.1秒ごとにnumを累積した結果を出力する必要がありますが、2つのプロセスp1とp2に異なる累積値が設定されています。それでは、これら2つのプロセスの間に競合が発生するかどうかを見てみましょう。

それを実行します:

1
4
5
8
9
12
13
16
17
20

プロセス1とプロセス2が共有メモリvの使用を急いでいることがわかります。、各プロセスは共有メモリアクセスを取得し、累積値を提供します。共有メモリが同時に使用され、同時に値が追加されて重複が発生する場合がありますが、プロセスロックを追加することで解決できます。

プロセスロックを追加する

さまざまなプロセスが共有リソースを取得するという上記の問題を解決するために、プロセスロックを追加することで問題を解決できます。

#在job()中设置进程锁的使用,保证运行时一个进程的对锁内内容的独占
def job(v, num, l):
    l.acquire() # 锁住
    for _ in range(5):
        time.sleep(0.1) 
        v.value += num # v.value获取共享内存
        print(v.value)
    l.release() # 释放
    
def multicore():
    # 定义一个进程锁
然后将进程锁的信息传入各个进程中
    l = mp.Lock() 
    v = mp.Value('i', 0) # 定义共享内存
    p1 = mp.Process(target=job, args=(v,1,l)) # 需要将lock传入
    p2 = mp.Process(target=job, args=(v,3,l)) 
    p1.start()
    p2.start()
    p1.join()
    p2.join()
if __name__ == '__main__':
    multicore()

それを実行して、リソースをプリエンプトする状況がまだあるかどうかを見てみましょう。

1
2
3
4
5
8
11
14
17
20

明らかに、プロセスロックが追加された後、他のプロセスは最初の累積プロセス中に共有メモリを取得しないため、プロセスp1の完全な動作が保証され、p1に基づくプロセスp2が実行されます。値はすでに計算してから計算に進みます。プロセスロックは、複数のプロセスが存在する場合にプロセスが互いに干渉しないことを保証します。

おすすめ

転載: blog.csdn.net/lockhou/article/details/114156553