質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

2回答

7641閲覧

【Python】マルチスレッドを維持しつつ、それぞれのスレッドで定期処理を行いたい

_ng.h.ks_

総合スコア13

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2019/04/04 01:07

編集2019/04/05 01:06

前提・実現したいこと

※Python学習を始めたばかりです。
マルチスレッドそれぞれで定期処理をするプログラムをいろいろ試してみたくて書いたソースです。
作成した内容では、suchedule_event_communicateとsuchedule_event_run_print_helloの
スレッドを立ち上げています。
それぞれで、3秒に一回時刻を表示、5秒に一回"HELLO!!!"を表示(ただし、処理の途中で10秒間待機させる)という内容になっています。

発生している問題・エラーメッセージ

下記のソースを実行すると、以下のメッセージのように、
同じ時刻で同じメッセージが二回表示されます。

2019-04-04 09:37:06.219 : get_time 2019-04-04 09:37:08.247 : HELLO!!! 2019-04-04 09:37:08.247 : HELLO!!! 2019-04-04 09:37:08.247 : 待機状態です、、、 2019-04-04 09:37:08.247 : 待機状態です、、、 2019-04-04 09:37:11.258 : 待機状態です、、、 2019-04-04 09:37:11.258 : 待機状態です、、、 2019-04-04 09:37:14.268 : 待機状態です、、、 2019-04-04 09:37:14.268 : 待機状態です、、、 2019-04-04 09:37:17.279 : 待機状態です、、、 2019-04-04 09:37:17.279 : 待機状態です、、、 2019-04-04 09:37:20.290 : print_hello_exit 2019-04-04 09:37:20.290 : print_hello_exit 2019-04-04 09:37:21.304 : get_time 2019-04-04 09:37:21.304 : get_time 2019-04-04 09:37:24.346 : get_time 2019-04-04 09:37:25.360 : HELLO!!! 2019-04-04 09:37:25.360 : 待機状態です、、、 2019-04-04 09:37:25.360 : HELLO!!! 2019-04-04 09:37:25.360 : 待機状態です、、、

該当のソースコード

Python

1import schedule 2import time 3import concurrent.futures 4 5from datetime import datetime, timedelta 6 7 8# ******************************** 9# 関数 10# ******************************** 11 12 13def multi_thread(): 14 executor = concurrent.futures.ThreadPoolExecutor(max_workers=5) 15 executor.submit(suchedule_event_communicate) 16 executor.submit(suchedule_event_run_print_hello) 17 18 19def get_timestring(): 20 """ 21 ミリ秒までを保持できる現在時刻を取得 22 :return: 23 """ 24 now = datetime.now() 25 return now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] 26 27 28def print_message(run_time=get_timestring(), message=""): 29 print(run_time + " : " + message) 30 31 32def suchedule_event_communicate(): 33 """ 34 3秒に一回時刻を表示する 35 :return: 36 """ 37 schedule.every(3/60).minutes.do(print_time) 38 while True: 39 schedule.run_pending() 40 time.sleep(1) 41 42 43def print_time(): 44 print_message(get_timestring(), "get_time") 45 46 47def suchedule_event_run_print_hello(): 48 """ 49 5秒に一回HELLO!!!を表示する 50 :return: 51 """ 52 schedule.every(5/60).minutes.do(print_hello) 53 while True: 54 schedule.run_pending() 55 time.sleep(1) 56 57 58def print_hello(): 59 now_time = get_timestring() 60 print_message(now_time, "HELLO!!!") 61 62 # HELLO!!!が表示されてから10秒間は待機時間 63 while str(datetime.strptime(now_time, '%Y-%m-%d %H:%M:%S.%f') + timedelta( 64 seconds=10)) > get_timestring(): 65 print_message(get_timestring(), "待機状態です、、、") 66 time.sleep(3) 67 68 print_message(get_timestring(), "print_hello_exit") 69 70 71# ******************************** 72# main 73# ******************************** 74 75 76# 定期処理起動 77multi_thread() 78 79

試したこと

マルチスレッドでwhileを使用する以下の処理であれば交互にメッセージを出力(同じ時刻・メッセージなし)しました。
scheduleの中で、whileや待機(sleep)と組み合わせるのが良くないのでしょうか。

Python

1import time 2import concurrent.futures 3from datetime import datetime 4 5 6def get_timestring(): 7 """ 8 ミリ秒までを保持できる現在時刻を取得 9 :return: 10 """ 11 now = datetime.now() 12 return now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] 13 14 15def print_message(run_time=get_timestring(), message=""): 16 print(run_time + " : " + message) 17 18 19def func1(): 20 while True: 21 print_message(get_timestring(), "func1") 22 time.sleep(1) 23 24 25def func2(): 26 while True: 27 print_message(get_timestring(), "func2") 28 time.sleep(1) 29 30 31if __name__ == "__main__": 32 executor = concurrent.futures.ThreadPoolExecutor(max_workers=2) 33 executor.submit(func1) 34 executor.submit(func2) 35

試したこと②

suchedule_event_run_print_hello内に記載されていたwhileをなくしました。
そうするとうまくいくようです。

Python

1import schedule 2import time 3import concurrent.futures 4 5from datetime import datetime, timedelta 6 7 8# ******************************** 9# 関数 10# ******************************** 11 12 13def multi_thread(): 14 executor = concurrent.futures.ThreadPoolExecutor(max_workers=5) 15 executor.submit(suchedule_event_communicate) 16 executor.submit(suchedule_event_run_print_hello) 17 18 19def get_timestring(): 20 """ 21 ミリ秒までを保持できる現在時刻を取得 22 :return: 23 """ 24 now = datetime.now() 25 return now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] 26 27 28def print_message(run_time=get_timestring(), message=""): 29 print(run_time + " : " + message) 30 31 32def suchedule_event_communicate(): 33 """ 34 3秒に一回時刻を表示する 35 :return: 36 """ 37 schedule.every(3/60).minutes.do(print_time) 38 while True: 39 schedule.run_pending() 40 time.sleep(1) 41 42 43def print_time(): 44 print_message(get_timestring(), "get_time") 45 46 47def suchedule_event_run_print_hello(): 48 """ 49 5秒に一回HELLO!!!を表示する 50 :return: 51 """ 52 schedule.every(5/60).minutes.do(print_hello) 53  # whileをなくしました。 54 schedule.run_pending() 55 time.sleep(1) 56 57 58def print_hello(): 59 now_time = get_timestring() 60 print_message(now_time, "HELLO!!!") 61 62 # HELLO!!!が表示されてから10秒間は待機時間 63 while str(datetime.strptime(now_time, '%Y-%m-%d %H:%M:%S.%f') + timedelta( 64 seconds=10)) > get_timestring(): 65 print_message(get_timestring(), "待機状態です、、、") 66 time.sleep(3) 67 68 print_message(get_timestring(), "print_hello_exit") 69 70 71# ******************************** 72# main 73# ******************************** 74 75 76# 定期処理起動 77multi_thread() 78 79
2019-04-05 10:00:37.653 : get_time 2019-04-05 10:00:39.653 : HELLO!!! 2019-04-05 10:00:39.661 : 待機状態です、、、 2019-04-05 10:00:42.661 : 待機状態です、、、 2019-04-05 10:00:45.661 : 待機状態です、、、 2019-04-05 10:00:48.662 : 待機状態です、、、 2019-04-05 10:00:51.662 : print_hello_exit 2019-04-05 10:00:52.662 : get_time 2019-04-05 10:00:55.662 : get_time 2019-04-05 10:00:56.662 : HELLO!!! 2019-04-05 10:00:56.663 : 待機状態です、、、 2019-04-05 10:00:59.663 : 待機状態です、、、

補足情報(FW/ツールのバージョンなど)

Python 3.7.0(pycharm使用)
windows7 64bit

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

質問のコードにあるscheduleはglobal変数です。そのためscheduleには2つのjobが定義されてしまい、それが2つのthreadで動作しているので、二重に実行されてしまいます。
その問題を解消するためには以下のように thread 毎にクラスSchedulerのインスタンスを作ればいいと思われます。

python

1def suchedule_event_communicate(): 2 sc1 = schedule.Scheduler() 3 sc1.every(3/60).minutes.do(print_time) 4 while True: 5 sc1.run_pending() 6 time.sleep(1) 7 8def suchedule_event_run_print_hello(): 9 sc2 = schedule.Scheduler() 10 sc2.every(5/60).minutes.do(print_hello) 11 while True: 12 sc2.run_pending() 13 time.sleep(1) 14

投稿2019/04/05 07:25

YasuhiroNiji

総合スコア584

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dodox86

2019/04/05 07:56

完全にかぶりました。失礼しました。
_ng.h.ks_

2019/04/09 00:33

先に投稿いただけておりましたので、ベストアンサーにさせていただきました。 ご回答くださいましてありがとうございました。
guest

0

原因としてはscheduleモジュールの関数版のschedule.everyschedule.run_pendingを複数のスレッドで使っているためです。

scheduleモジュールのschedule.every関数を実行するとき、スケジューラーとして使用されるscheduleモジュール内の変数default_schedulerがグローバルとなっており、各スレッドでschedule.everyを使うと、それぞれのスレッドでひとつのスケジューラーを共用するかたちになります。

github schedule - init.py

スレッドそれぞれでスケジューラーを実行するため、同じ文字列が表示されてしまいます。質問者さんのコードではconcurrent.futures.ThreadPoolExecutorで生成したスレッドプールのスレッド2つが動きますが、それぞれで同じスケジュールを実行してしまうため、2つの"HELLO!!!"文字列がほぼ同時に表示されます。同じ時刻に見えるのはたまたまです。スレッドを3つにすると、今度は3つの"HELLO!!!"文字列が確認できます。

スレッドそれぞれでJOBをスケジューリングするのであれば、schedule.everyではなく、Scheduleクラスのインスタンスメソッドのeveryを使わなければなりません。また、schedule.run_pendingも同様です。上記で示した scheduleモジュールのソースファイル「init.py」を読むと分かります。

本件とは直接関係ありませんが、確認の際はthreading.get_identでスレッドのIDを表示させるようにすると分かり易くなります。例えばprint_message
適用すると、以下のようなかんじになります。

Python3

1import threading 2 3def print_message(run_time=get_timestring(), message=""): 4 #print(run_time + " : " + message) 5 print('{} (id={}): {}'.format(run_time, threading.get_ident(), message)) 6

一部抜粋になりますが、質問者さんのコードをもとにして、以下のように修正します。

Python3

1import threading 2 3... 4 5def print_message(run_time=get_timestring(), message=""): 6 #print(run_time + " : " + message) 7 print('{} (id={}): {}'.format(run_time, threading.get_ident(), message)) 8 9 10def suchedule_event_communicate(): 11 """ 12 3秒に一回時刻を表示する 13 :return: 14 """ 15 # schedule.everyではなく、インスタンスメソッドのeveryを使う 16 scheduler = schedule.Scheduler() 17 scheduler.every(3/60).minutes.do(print_time) 18 while True: 19 # schedule.run_pending ではなく、インスタンスメソッドのrun_pendingを使う 20 scheduler.run_pending() 21 time.sleep(1) 22

このようにすると、以下のように質問者さんの意図通り実行されます。

bash

1$ python3 t5.py 22019-04-05 16:25:20.673 (id=-2146563000): get_time 32019-04-05 16:25:22.674 (id=-2146563704): HELLO!!! 42019-04-05 16:25:22.689 (id=-2146563704): 待機状態です、、、 52019-04-05 16:25:23.673 (id=-2146563000): get_time 62019-04-05 16:25:25.690 (id=-2146563704): 待機状態です、、、 72019-04-05 16:25:26.673 (id=-2146563000): get_time 82019-04-05 16:25:28.691 (id=-2146563704): 待機状態です、、、 92019-04-05 16:25:29.674 (id=-2146563000): get_time 102019-04-05 16:25:31.691 (id=-2146563704): 待機状態です、、、 112019-04-05 16:25:32.674 (id=-2146563000): get_time 122019-04-05 16:25:34.691 (id=-2146563704): print_hello_exit 132019-04-05 16:25:35.674 (id=-2146563000): get_time 142019-04-05 16:25:38.674 (id=-2146563000): get_time 152019-04-05 16:25:39.691 (id=-2146563704): HELLO!!!

本件とは関係ないですが少し気になったところとしては、コードの一番下のメインスレッドでの実行は、すぐ終わってしまうことです。スレッドプールの2つのスレッドが起動したままなので気が付きにくいですが、メインは終わってしまってます。本来であれば何かしらThread.joinのようなもので待ち受けるべきだと思いますが、質問者さんの実験および習作のコードであろうし、別の課題として置いておきます。

投稿2019/04/05 07:55

dodox86

総合スコア9183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

_ng.h.ks_

2019/04/09 00:35

細かくご説明いただいてありがとうございました。 参考になりました。 本件以外のところでも、指摘をしていただけまして勉強になりました。 もう少し勉強してみます。 ご回答くださいましてありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問