Как правильно сделать посекундный счетчик для разных потоков в Python, используя tkinter?

Я пытаюсь создать приложение, которое при нажатии кнопки "Start" запускало бы новый поток. Для каждого потока ведется посекундный счет о времени его работы в реальном времени.

К сожалению, счетчик работает некорректно: с каждым новым запущенным потоком он увеличивается не на одну единицу, а на 2, 3, 4 и так далее.
Чем больше потоков запущено, тем больше увеличивается сам счетчик.
Например, если запущено десять потоков, счетчик каждую секунду увеличивается на 10 единиц, а не на одну, как нужно.

Подозреваю, что это как-то связано с рекурсией в коде, но не могу понять, как решить проблему?
Благодарю всех, кто откликнется.

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

threads = []
temp = 0
thread_number = 0
after_id = ""

def thread_start():

    global thread_number

    thread_number += 1
    new_thread = Thread(target=thread_func, args=(thread_number,))
    threads.append(new_thread)
    new_thread.start()

def thread_func(thread_number):

    temp = 0

    def tick():
        global temp, after_id

        label = tk.Label(win, text="Hello")
        label.grid(row=0+thread_number, column=0)
        after_id = win.after(1000, tick)
        label.configure(text=str(temp))
        temp += 1

    tick()

win = tk.Tk()
win.title("Threads counter example")
win.geometry("800x600")

tk.Button(win, text='Start', command=thread_start,).grid(row=0,column=1,stick='we')

win.mainloop()

Ответы (2 шт):

Автор решения: S. Nick

Попробуйте так:

import threading
# ?import time
import tkinter as tk
from  threading import Thread
# ?from datetime import datetime
# ?from time import time

threads = []              # ?
#temp = 0                 # ?
thread_number = 0
after_id = ""             # ?

labels = []                                                          # <---- !!!

def thread_start():
    global thread_number
#    thread_number += 1
    new_thread = Thread(target=thread_func, args=(thread_number,))
    threads.append(new_thread)
    new_thread.start()
    thread_number += 1                                               # +++

def thread_func(thread_number):
    #print(f'\nthread_number={thread_number}') # 
    
    temp = 0
    
    label = tk.Label(win, text="Hello")                              # +++
    label.grid(row=1+thread_number, column=0)                        # +++
    labels.append([label, temp])                                     # <---- !!!

    def tick():
        labels[thread_number][0].configure(                          # <---- !!!
            text=str(labels[thread_number][1]))                      # <---- !!!
            
        labels[thread_number][1] += 1                                # <---- !!!
        
        after_id = win.after(1000, tick)

    tick()


win = tk.Tk()
win.title("Threads counter example")
win.geometry("200x400+400+100")

tk.Button(win, text='Start', command=thread_start,).grid(row=0,column=1,stick='we')

win.mainloop()

введите сюда описание изображения

→ Ссылка
Автор решения: insolor

Вы скрестили два подхода: планирование выполнения через after и потоки. Если уж используете потоки, то можно обойтись без after (использовать обычные while True и time.sleep()). Если нет какого-то долгого блокирующего поток кода, то можно обойтись без потоков, и использовать просто after. Скрещивание этих двух вариантов возможно, но усложняет код.

Пример через потоки (код немного упростил, добавил daemon=True, чтобы потоки автоматически завершались при закрытии окна):

import time
import tkinter as tk
from  threading import Thread
import time

threads = []


def thread_start():
    thread_number = len(threads) + 1
    new_thread = Thread(target=thread_func, args=(thread_number,), daemon=True)
    threads.append(new_thread)
    new_thread.start()


def thread_func(thread_number):
    temp = 0
    label = tk.Label(win, text="Hello")
    label.grid(row=0+thread_number, column=0)

    while True:
        label.configure(text=str(temp))
        temp += 1
        time.sleep(1)


win = tk.Tk()
win.title("Threads counter example")
win.geometry("800x600")

tk.Button(win, text='Start', command=thread_start).grid(row=0, column=0, stick='we')

win.mainloop()

Через after без потоков:

import tkinter as tk

count = 0


def start():
    global count
    count += 1
    func(count)


def func(count):
    label = tk.Label(win, text="Hello")
    label.grid(row=0 + count, column=0)

    def tick(temp=0):
        label.configure(text=str(temp))
        temp += 1
        win.after(1000, tick, temp)
    
    tick()


win = tk.Tk()
win.title("Threads counter example")
win.geometry("800x600")

tk.Button(win, text='Start', command=start).grid(row=0, column=0, stick='we')

win.mainloop()
→ Ссылка