LoginSignup
0
1

Pythonの並列処理、threading

Last updated at Posted at 2024-02-15

1 音を鳴らしながら、次の処理させたい・・・

前提、背景

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

つまり、↓こんな感じ

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

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

2 何も考えずプログラム書いた(笑)

※beep音を鳴らすところはプリントに変えてます。うるさいでしょ。w

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関数を渡すだけ。

まず、スレッドクラスのインスタンスを起こして・・・

t1 = threading.Thread(target=beep)

そのスレッドを動かすのが基本らしい

t1.start()

よし、書いてこう

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が4になった時、beep関数を動かし始める

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ループにして書き換える

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_event1set()されていない時は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からまた鳴らしたい。

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に変更してます。

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講座。おすすめです。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1