minofoto and miscellaneous notes

ごく気まぐれに,書きたいことを適当に書いています。本当の話かもしれませんし,フィクションかもしれません。

Python でもマルチスレッドプログラムを書いてみたらハマった

昨日の Ruby/tk で書いたマルチスレッドプログラムを Python/tkinter に移植したら、予想外の挙動に出会いました。

Ruby/Tk でマルチスレッドプログラム - minofoto and miscellaneous notes

import tkinter as tk
import threading
from datetime import datetime
from time import sleep

def update_calendar(label):
    while True:
        now = datetime.now()
        label.configure(text="{:04d}/{:02d}/{:02d}".format(now.year, now.month, now.day))
        label.update()
        sleep(2)

def update_clock(label):
    while True:
        now = datetime.now()
        label.configure(text="{:02d}:{:02d}:{:02d}".format(now.hour, now.minute, now.second))
        label.update()
        sleep(1)

root = tk.Tk()
label1 = tk.Label(root, text="label1")
label2 = tk.Label(root, text="label2")
label1.pack()
label2.pack()

thread1 = threading.Thread(target=update_calendar(label1), daemon=True)
thread2 = threading.Thread(target=update_clock(label2), daemon=True)

thread1.start()
thread2.start()

root.mainloop()

これを実行すると

となってしまい、thread2 で更新されるはずの label2 に時間が表示されません。

これは、threading.Thread で引数の渡し方が間違っていたからのようです。正しくは

thread1 = threading.Thread(target=update_calendar, args=(label1,), daemon=True)
thread2 = threading.Thread(target=update_clock, args=(label2,), daemon=True)

と、引数を (arg, ) とタブルにして、args=(label1,) のように 渡すべきでした。Ruby と比べるとちょっとややこしいですね。とはいえ、元の間違いのように threading.Thread(target=function(arg)) とした場合でもエラーにならずに function(arg) を実行するというのは不思議な挙動です。thread2 が実行されなかったことから、おそらくサブスレッドではなくメインスレッドで update_calendar(label1) が実行され、そこが無限ループなので止まっていたのでしょう。

Python の threading でちょっと嵌ってしまうポイントでした。