

Pythonはほとんど使いませんが、友人のコードを見ていて変な箇所を見つけて調べて問題にあたりました。

<http://bty.sakura.ne.jp/wp/archives/71>

の例で

[code]

    import threading
    
    def hello():
        print "helohelo"
    
    t=threading.Timer(5,hello)
    t.start()
    
[/code]

というものがあります。5秒後に別スレッドでhello()を実行します。これは納得です。つづいて一定時間ごとに処理を繰り返すサンプルとして

[code]

    import threading
    
    def hello():
        print "helohelo"
        t=threading.Timer(1,hello)
        t.start()
    
    
    t=threading.Thread(target=hello)
    t.start()
    
[/code]

これに違和感を覚えます。hello()の中でまた5秒後に別スレッドを作りhello()を実行するコードが入っています。これを実行したら次々スレッドが生成されて、やがてリソースを食いつぶしてプログラムが落ちるのではないかと思うのですが、いかがでしょうか？

あれこれ調べていて

<http://www.webscalability.com/blog/2016/02/simple-example-of-using-the-
threading-timer-class-in-python/>

でも同じようにincの中でincを呼び出しています。

自分で簡単なコードを書いてみました。

[code]

    import threading
    import time
    
    def hello():
        print("現在のスレッドの数: " + str(threading.activeCount()))
        print("[%s] helohelo!!" % threading.currentThread().getName())
        t=threading.Timer(1,hello)
        t.start()
    
    if __name__=='__main__':
        t=threading.Thread(target=hello)
        t.start()
    
[/code]

実行結果は

[code]

    現在のスレッドの数: 2
    [Thread-1] helohelo!!
    現在のスレッドの数: 2
    [Thread-2] helohelo!!
    現在のスレッドの数: 2
    [Thread-3] helohelo!!
    現在のスレッドの数: 2
    [Thread-4] helohelo!!
    現在のスレッドの数: 2
    [Thread-5] helohelo!!
    現在のスレッドの数: 2
    [Thread-6] helohelo!!
    現在のスレッドの数: 2
    [Thread-7] helohelo!!
    
[/code]

でした。Threadの名前は毎回異なっていて別スレッドが作られているようですが、スレッドの数自体は2から増えていないののでこれで問題ないのでしょうか？

追記：再帰呼び出しの形になっていますが、別スレッドで動いているためthreading.Timer(1,hello)の直後に親スレッドは消えてしまうことによってこのコードは安定して動いていると言うことでしょうか？こういう書き方はスレッド生成元が子スレッドの終了を見て再起動させるとか、スレッドの中で無限ループ＋sleepさせるよりスマートなのかも知れません。

質問がぼやけてしまったので再確認しますが、Pythonを使い慣れている方にとってこの末尾で自分自身を別スレッドで走らせるというのは自然なのでしょうか？


CC BY-SA 3.0
2016-05-02T05:49:51.617
24508
2016-05-02T19:35:36.953
2016-05-02T06:37:51.263
14631
14631
question
python
Pythonのthreading.Timerで定期的に処理を呼び出すサンプル


スレッド数の挙動に関しては`hello`関数内で`t.start()`がよばれた後そのスレッドは破棄されます．
言及されているように`t.start()`の後に無限ループするコードを追加するとスレッド数が増え続けるのがわかります．
またスレッド数が2のままなのはPython本体のmainスレッドとそのとき動作しているhello関数のスレッドが同時に動作してるからです．


CC BY-SA 3.0
2016-05-02T11:10:09.817
24515
2016-05-02T11:10:09.817
14418
24508
answer


以下のコードを書いてみました。

[code]

    #!/usr/bin/python3
    
    import threading
    import time
    
    def hello(thr_no):
      time.sleep(2.5)
      print(thr_no)
    
    def hello1():
      t = threading.Timer(3, hello1)
      t.start()
      hello("hello1")
    
    def hello2():
      while True:
        time.sleep(3)
        hello("hello2")
    
    if __name__=='__main__':
      t1 = threading.Thread(target=hello1)
      t1.start()
      t2 = threading.Thread(target=hello2)
      t2.start()
    
[/code]

`hello1()` では 3 秒ごとにスレッドを作成していて、`hello2()` では単一スレッド内で 3
秒間のスリープを挟んでいます。実行結果を見て貰えば分かるかと思いますが、`hello2()`(無限ループ)では次回の実行開始が
(タイマー時間+関数の処理時間) になってしまいます。ただ、`hello1()`
でも新規スレッドの作成を関数の最後にしてしまうと同じことになってしまいます。

つまり、「末尾で自分自身を別スレッドで走らせる」のではなく、タイマーに登録した関数の最初で別スレッドを作成して実行を開始する方が「タイマー機能の正確さが無限ループ+スリープよりは向上する」という意味では適しているかと思います。

話は変わりますが、定期的に処理を呼び出すためには Timer
クラスを継承したクラスを作成するという方法もあります。多重実行のチェックなどはしていないので不完全ですが、参考までにどうぞ。

**repeated_timer.py**

[code]

    #!/usr/bin/python3
    
    from threading import Timer
    
    class RepeatedTimer(Timer):
      def __init__(self, interval, function, args=[], kwargs={}):
        Timer.__init__(self, interval, self.run, args, kwargs)
        self.thread = None
        self.function = function
    
      def run(self):
        self.thread = Timer(self.interval, self.run)
        self.thread.start()
        self.function(*self.args, **self.kwargs)
    
      def cancel(self):
        if self.thread is not None:
          self.thread.cancel()
          self.thread.join()
          del self.thread
    
    if __name__=='__main__':    
    
      import time
    
      def hello(message):
        hello.counter += 1
        print(message, hello.counter)
      hello.counter = 0
    
      t = RepeatedTimer(0.5, hello, ["Hello"])
      t.start()
      time.sleep(5)
      t.cancel()
      print("Done.")
    
[/code]


CC BY-SA 3.0
2016-05-02T19:35:36.953
24529
2016-05-02T19:35:36.953
24508
answer