Pythonの並列処理、threading

# 1　音を鳴らしながら、次の処理させたい・・・
### 前提、背景
パソコンが他の設備とシリアル通信しているとする。
例えば異常が返ってきた時、警報を鳴らしたいなぁ・・・
でも、シリアル通信を毎秒繰り返しているのは止めたくないなぁ

つまり、↓こんな感じ

|  time(s)  |  serial  |  beep  |
| ---- | ---- | ---- |
|  1  |  正常  |  --  |
|  2  |  正常  |  --  |
|  3  |  正常  |  --  |
|  4  |  異常  |  beep  |
|  ・・・  |  ・・・　 |  ・・・  |
|  15  |  異常　 |  beep  |
|  16  |  異常  |  beep  |
|  17  |  正常  |  --  |
|  18  |  正常  |  --  |
|  19  |  正常  |  --  |
|  20  |  正常  |  --  |

beep音は短い音を繰り返し鳴らせて、正常になったら止めたい。
そして、また異常が来たらbeep音を鳴らしたい。
というモチベーションの中、試行錯誤した記録。


# 2　何も考えずプログラム書いた（笑）
※beep音を鳴らすところはプリントに変えてます。うるさいでしょ。w

```python
import time

def beep():
    for i in range(20):
        print(i, 'beep')
        time.sleep(0.5)

def serial_connect():
    for i in range(20):
        print(i, 'correct')
        time.sleep(0.5)

if __name__ == '__main__':
    beep()
    serial_connect()
```

で、出力
```
0 beep
1 beep
2 beep
3 beep
4 beep
5 beep
6 beep
7 beep
8 beep
9 beep
10 beep
11 beep
12 beep
13 beep
14 beep
15 beep
16 beep
17 beep
18 beep
19 beep
0 correct
1 correct
2 correct
3 correct
4 correct
5 correct
6 correct
7 correct
8 correct
9 correct
10 correct
11 correct
```
俺はアホか・・・w
そりゃ、順番に処理されるさ。w


# 3　並行処理をやってみた
Pythonの```threading```ライブラリの```Thread```クラスの引数```target```に並列処理をしたい関数を入れてインスタンスを立てて、
そのインスタンスに```start```関数を渡すだけ。


まず、スレッドクラスのインスタンスを起こして・・・
```python
t1 = threading.Thread(target=beep)
```
そのスレッドを動かすのが基本らしい
```python
t1.start()
```

よし、書いてこう

```python
import time
import threading

def beep():
    for i in range(20):
        print(i, 'beep')
        time.sleep(0.5)

def serial_connect():
    for i in range(12):
        print(i, 'correct')
        time.sleep(0.5)

if __name__ == '__main__':
    t1 = threading.Thread(target=beep)
    t2 = threading.Thread(target=serial_connect)
    
    t1.start()
    t2.start()
```


んで、出力


```
0 beep
0 correct
1 beep
1 correct
2 beep
2 correct
3 beep
3 correct
4 beep
4 correct
5 beep
5 correct
6 beep
6 correct
7 beep
7 correct
8 beep
8 correct
9 beep
9 correct
10 beep
10 correct
11 beep
11 correct
12 beep
13 beep
14 beep
15 beep
16 beep
17 beep
18 beep
19 beep
```
入り混じってきたぞい。
二つの関数が並行して動いてる。
つまり、異常が起きた時に```t1.start()```すればいいのね。

# 4 「i」が「4」にビープ音を鳴らし始めたい
ということで、以下のように書いてみた。
for文で、correctをprint。iが４になった時、```beep```関数を動かし始める


```python
import time
import threading

def beep():
    for i in range(20):
        print(i, 'beep')
        time.sleep(0.5)

if __name__ == '__main__':
    t1 = threading.Thread(target=beep)
    
    for i in range(12):
        print(i, 'correct')
        if i == 4:
            t1.start()
        time.sleep(0.5)

```

出力
```
0 correct
1 correct
2 correct
3 correct
4 correct
0 beep
1 beep
5 correct
2 beep
6 correct
3 beep
7 correct
8 correct
4 beep
9 correct
5 beep
6 beep
10 correct
11 correct
7 beep
8 beep
9 beep
10 beep
11 beep
12 beep
13 beep
14 beep
15 beep
16 beep
17 beep
18 beep
19 beep
```

# 5　並行処理しているのを止めてみたい
ということで、beep関数を```while```ループにして書き換える

```python
import time
import threading


def beep(event):
    while not event.is_set():
        print('beep')
        time.sleep(0.5)
    print('beep音、おしまい')


if __name__ == '__main__':
    stop_event1 = threading.Event()
    t1 = threading.Thread(target=beep, args=(stop_event1,))

    for i in range(20):
        print(i, 'correct')
        time.sleep(0.5)
        if i == 4:
            print('異常発生！')
            t1.start()
        elif i == 12:
            stop_event1.set()　# ここでset()することでbeep関数のwhileループから抜ける
```
ちょっと解説したほうが良さそう
まず```stop_event1```として、```threading.Event()```クラスを立てておく。
これが、```Thread```クラスの中に入り込めるらしい。ふむふむ

で、次がbeep関数
```event```変数を受け取れるようにしておく。
これは先の```stop_event1```関数を受け取るためのもの。そしてこの```stop_event1```が```set()```されていない時は```while```文が回り続け、```set()```されていると```while```文を抜ける仕組み。

そして、Threadクラスのインスタンスを立てるときに、引数として```stop_event1```を渡しておく。
こうすることで、```elif```の中で```stop_event1.set()```されると```beep```関数に渡されて止まるという仕組み。

じゃ、レッツゴ〜
```
0 correct
1 correct
2 correct
3 correct
4 correct
異常発生！
beep
5 correct
6 correct
beep
7 correct
beep
beep
8 correct
beep
9 correct
10 correct
beep
11 correct
beep
12 correct
beep
13 correct
beep音、おしまい
14 correct
15 correct
16 correct
17 correct
18 correct
19 correct
```
よく考えたら「異常」って出力しているのに「correct」っておかしいよね。w

これで、並行処理を自由に止めれるようになりました。
が、まだ問題がある。

# 6 何度も鳴らしたい
ということで、こんな感じでi==12で止めて、i==15からまた鳴らしたい。

```python
import time
import threading


def beep(event):
    while not event.is_set():
        print('beep')
        time.sleep(0.5)


if __name__ == '__main__':
    stop_event1 = threading.Event()
    t1 = threading.Thread(target=beep, args=(stop_event1,))

    for i in range(20):
        print(i, 'correct')
        time.sleep(0.5)
        if i == 4:
            print('異常発生！')
            t1.start() # 1回目
        elif i == 12:
            stop_event1.set()
        elif i == 15:
            print('異常発生！')
            t1.start() # 2回目
```
こうやって2回目を鳴らそうとすると
```
RuntimeError: threads can only be started once
```
「threadは一度しかスタートできないよ（ハート）」
なんだって！

ということで、ちょっと改良。
※鳴らすタイミングをiが4、と10の時、止めるタイミングを7、15に変更してます。
```python
import time
import threading


def beep(event):
    while not event.is_set():
        print('beep')
        time.sleep(0.5)

if __name__ == '__main__':

    for i in range(20):
        print(i, 'correct')
        time.sleep(0.5)
        if i == 4:
            stop_event1 = threading.Event()
            print('異常発生！')
            t1 = threading.Thread(target=beep, args=(stop_event1,))
            t1.start() # 1回目
        elif i == 7:
            print('beep音終了')
            stop_event1.set()
        elif i == 10:
            stop_event1 = threading.Event()
            t1 = threading.Thread(target=beep, args=(stop_event1,))
            print('異常発生！')
            t1.start() # 2回目
        elif i == 15:
            print('beep音終了')
            stop_event1.set()
        else:
            pass
```
スタート前に毎回インスタンスを起こして、stop_event1のインスタンスも起こす。

で再度、れっつらご〜！
```
0 correct
1 correct
2 correct
3 correct
4 correct
異常発生！
beep
5 correct
beep
6 correct
7 correct
beep
beep
beep音終了
8 correct
9 correct
10 correct
異常発生！
beep
11 correct
beep
12 correct
13 correct
beep
14 correct
beep
15 correct
beep
beep
beep音終了
16 correct
17 correct
18 correct
19 correct
```
むむむ、止まる時、微妙に合わないのね。
というか、並行処理だからこういうことも起きるのかもね。
並行処理はスタートは決められるけど、終わりは処理によってちょっとズレる可能性があるのかもしれん。
その時の他の処理の動作にもよるんだろうな。


# 　終わりに
ということで、Qiita初投稿はthreadingでした。あまりthreadingに関する記事や情報が多くないんですよね。
こういう時はどうしたらいいんだろうと色々やってみると「ほほー」となってきます。
参考にしたのは酒井潤さんのUdemy講座。おすすめです。

https://www.udemy.com/course/python-beginner/?utm_source=adwords&utm_medium=udemyads&utm_campaign=LongTail_la.JA_cc.JP&utm_content=deal4584&utm_term=_._ag_107181210924_._ad_452531407122_._kw__._de_c_._dm__._pl__._ti_aud-2268488108799%3Adsa-930814700879_._li_1009461_._pd__._&matchtype=&gad_source=1&gclid=Cj0KCQiA5rGuBhCnARIsAN11vgSpP7SiT3RnfhBrLrenPS5K_GxQgmUWzosis2ZAruanjGXM4BfKxJwaAou8EALw_wcB