python#Multiprocessing

Multiprocessing について話ししましょう。

この間threadingのこと書きましたが、ではいつmultiprocessing使うのか、いつthreading を使うのかも前の記事で書いておきました。multiprocessing はあなたのプログラムをspeed up するために使われていて、じゃどうやってspeed up かどういうといろんなタスクを同時に走られるってことです。

threading↓
https://qiita.com/soup01/items/42810b541fc3adc09542

最初またdo_something()を見てみよう。

結果はもちろん1秒まって終わるでしょう。

```python
import time

start=time.perf_counter()

def do_something():
	print('sleeping 1 s..')
	time.sleep(1)
	print('Wake up!')

do_something()
do_something()

finish=time.perf_counter()
print('finish on {}'.format(finish-start))
```
タイムチャートは以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154661/710e9bc9-332f-8131-04c6-9c10c4d17016.png)

じゃmultiprocessing を使うとどうになるの？
このmodule が複数のタスクを切り分けてほかのcpus に走らせる。ここでcpu bounds が出てきましたね。cpu bounds はたくさんの計算がありcpuに負荷かかるタスクであります。
タイムチャートはこうになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154661/87497a89-da9b-6b50-194d-79a38591855b.png)

cpu bounds をthreading を使ってもこんなに早くならないのはそれらのthreading はまだ"一つのプロセス"だけ走ってます。なのでまた繰り返しになりますが、multiprocessing があなたのタスクを切り分けてマルチに走らせるんです。でも、結局もあなたのコンピュータのハードウェアの能力によりますけどねー
ここでmultiprocessingのモジュールを使ってみよう。

```python
import time
import multiprocessing

start=time.perf_counter()

def do_something():
	print('sleeping 1 s..')
	time.sleep(1)
	print('Wake up!')

#create 2 process
p1=multiprocessing.Process(target=do_something)
p2=multiprocessing.Process(target=do_something)

p1.start()
p2.start()

finish=time.perf_counter()
print('finish on {}'.format(finish-start))
```
あれ？なんか0秒で終わっちゃったと気がするが…それはスクリプトの中にstart()し、sleeping()してる間に私たちのプログラムが走り続けて勝手に2つのプロセスが終わるまで終わるの時間の計算のラインを走ってしまった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154661/128551bc-0505-3bf7-de32-a93a05ed7999.png)

この現象をなくすためにjoin()を使えましょう。start ()のあとにjoin()。

```python
import time
import multiprocessing

start=time.perf_counter()

def do_something():
	print('sleeping 1 s..')
	time.sleep(1)
	print('Wake up!')

#create 2 process
p1=multiprocessing.Process(target=do_something)
p2=multiprocessing.Process(target=do_something)

p1.start()
p2.start()

p1.join()
p2.join()

finish=time.perf_counter()
print('finish on {}'.format(finish-start))
```
ちゃんとできましたね！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154661/903bc762-fde6-e57f-61e4-acf61474b271.png)

なんとかできましたが、あまり効果見えませんね。だってただ1秒まってるだけだもん。今度10回を回ったらどうになるかな？
その前になんでjoin()はlooping の中に使えないでしょうか？もし全てのプロセスがstart()するまえに勝手にjoin()をしたら、もう普通の同期操作と同じになる。
あときになるのはわたしのパソコンは10coresがないのに10のタスク走るのはどういうこと？と思いませんか？それはモジュールが勝手にタスクの間にスイッチしてタスクを切り替えるからです。

```python
import time
import multiprocessing

start=time.perf_counter()

def do_something():
	print('sleeping 1 s..')
	time.sleep(1)
	print('Wake up!')

processes=[]

for _ in range(10):
	p=multiprocessing.Process(target=do_something)
	p.start()
	processes.append(p)
  

for process in processes:
	process.join()


finish=time.perf_counter()
print('finish on {}'.format(finish-start))
```
結果はこうになります：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154661/fd951b35-102f-d10e-2f55-7a293e594a02.png)

じゃsleepの時間をパラメータとして入れよう。

```python
import time
import multiprocessing

start=time.perf_counter()

def do_something(s):
	print('sleeping {} s..'.format(s))
	time.sleep(s)
	print('Wake up!')

processes=[]

for _ in range(10):
	p=multiprocessing.Process(target=do_something,args=[1.5])
	p.start()
	processes.append(p)
  

for process in processes:
	process.join()


finish=time.perf_counter()
print('finish on {}'.format(finish-start))
```
結果はこうになりますね：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154661/365ba552-fcc7-8258-fe00-157616d3162b.png)

Python 3.2 からもっと簡単な方法でmultiprocessing をするの方法が誕生しました。じゃ最初からこの方法紹介すればいいじゃない？と思いかもしれませんが、やっはりまず手で従来あるモジュールを使って動きをある程度把握してからしたほうがいいかなって。submit() method がdo_something()というfunction を実行するタイミングをスケジュールしてfuture objectを返してくれるんです。なのでこのfuture object がfunction の実行状態などを管理するんです。終わったとか走ってるとか。

```python
import time
import concurrent.futures

start=time.perf_counter()

def do_something(s):
	print('sleeping {} s..'.format(s))
	time.sleep(s)
	return 'Wake up!'


with concurrent.futures.ProcessPoolExecutor() as executor:
	f1=executor.submit(do_something,1)
	print(f1.result())

finish=time.perf_counter()
print('finish on {}'.format(finish-start))
```
では10回をLoopingしてみよう。

```python
import time
import concurrent.futures

start=time.perf_counter()

def do_something(s):
	print('sleeping {} s..'.format(s))
	time.sleep(s)
	return 'Wake up!'


with concurrent.futures.ProcessPoolExecutor() as executor:

	results=[executor.submit(do_something,1) for _ in range(10)]

	for f in concurrent.futures.as_completed(results):
		print(f.result())


finish=time.perf_counter()
print('finish on {}'.format(finish-start))
```
今度はSleepの秒数それぞれ違うものをいれましょう。

```python
import time
import concurrent.futures

start=time.perf_counter()

def do_something(s):
	print('sleeping {} s..'.format(s))
	time.sleep(s)
	return 'Wake up!{} s..'.format(s)


with concurrent.futures.ProcessPoolExecutor() as executor:
	seconds=[5,4,3,2,1]
	results=[executor.submit(do_something,second) for second in seconds]

	for f in concurrent.futures.as_completed(results):
		print(f.result())


finish=time.perf_counter()
print('finish on {}'.format(finish-start))
```

結果はこうになりますね：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154661/fe73a6bf-9ac9-056b-d77e-2429f30d5dc8.png)

最後はMap()を使ってやります。基本的にはmap functionはリスト数の分でdo_something()を走ります。subbmit()はfutures objectが戻り値でmapはResultしか返せない。

```python
import time
import concurrent.futures

start=time.perf_counter()

def do_something(s):
	print('sleeping {} s..'.format(s))
	time.sleep(s)
	return 'Wake up!{} s..'.format(s)


with concurrent.futures.ProcessPoolExecutor() as executor:
	seconds=[5,4,3,2,1]
	results=executor.map(do_something,seconds)

	for result in results:
		print(result)

finish=time.perf_counter()
print('finish on {}'.format(finish-start))
```

ふーお疲れさま。