Seite 1 von 1

Lock für einen globalen Zähler nötig?

Verfasst: Freitag 17. Juli 2020, 06:52
von ppp
Hallo,
brauche ich einen Lock für den Counter finishedItems? Habe fünf Worker Threads, die sich Elemente aus einer Queue holen. Der Counter gibt an wieviele Elemente bearbeitet wurden. Ein Logger Thread gibt alle 5 Sekunden diese Variable. Ich bin der Meinung, dass ich keinen Lock angeben muss, oder?
finishedItems +=1 und print("Finshed items: "+str(finishedItems)) sind für mich atomar und können nicht zwischendrin unterbrochen werden. Oder sehe ich das falsch?

Code: Alles auswählen

import queue
import threading
import time

def worker():
	global finishedItems
	while(True):
	# do something with the element of the queue q
		try:
			element=q.get(False)
		except queue.Empty:
			break
		finishedItems +=1
		time.sleep(1)

def log():
	global finishedItems
	while(True):
		time.sleep(5)
		print("Finshed items: "+str(finishedItems))
		if q.empty():
			 break

finishedItems=0
q=queue.Queue()
for i in range(100000):
    q.put(i)

for i in range(5):
    x=threading.Thread(target=worker)
    x.start()

logThread = threading.Thread(target=log )
logThread.start()
logThread.join()

Re: Lock für einen globalen Zähler nötig?

Verfasst: Freitag 17. Juli 2020, 07:11
von sparrow
Vergiss dass es global gibt und benutze zur Kommunikation zwischen Threads, so wie du es bereirs machst eine Queue.

Re: Lock für einen globalen Zähler nötig?

Verfasst: Freitag 17. Juli 2020, 07:13
von Sirius3
Bevor Du anfängst, mit Threads zu arbeiten, solltest Du erst einmal lernen, wie man sauber Funktionen schreibt. Schon in Deinem anderen Thread, habe ich geschrieben, dass alles was eine Funktion braucht, über ihre Argumente kommen muß. Jetzt fängst Du auch noch mit `global` an, was man nie und bei Multithreading erst recht nie benutzen sollte.
Schreib erst einmal Dein Programm so um, dass es saubere Funktionen und eine main-Funktion hat und dann kann man sich überlegen, wie man mit einem finished_items (Variablennamen scheibt man komplett klein) umgeht. Dabei nicht vergessen, dass immer mit 4 Leerzeichen pro Ebene eingerückt wird und auf keinen Fall Tabs mit Leerzeichen gemischt werden sollten.

Re: Lock für einen globalen Zähler nötig?

Verfasst: Freitag 17. Juli 2020, 07:16
von Jankie
Sonstige Anmerkungen:

-Eingerückt wird mit 4 Leerzeichen
-Variablennamen sollten aussagekräftig sein
-Variablen werden laut Konvention klein_mit_unterstrich geschrieben
-Strings setzt man nicht mit + zusammen. Dafür gibt es Stringformatierung.
-Es sollte kein Code (Bis auf Konstanten) auf Modulebene stehen. Pack das einfach in eine main() Funktion.

Re: Lock für einen globalen Zähler nötig?

Verfasst: Freitag 17. Juli 2020, 07:46
von __deets__
Zusätzlich zu dem gesagten: die siehst das falsch. Da braucht es zum inkrement ein lock. Print ist? danach ja nicht kritisch.

Re: Lock für einen globalen Zähler nötig?

Verfasst: Freitag 17. Juli 2020, 09:01
von ppp
Danke für das Feedback. Habe den Code überarbeitet.
Bei der Übergabe von Parametern an Funktionen war ich mir nicht sicher. Ausschlaggebend war, dass in der Python-Doku in https://docs.python.org/3/library/queue.html(siehe unter join) von der Funktion worker auf eine globale Queue zugegriffen wird. Und wenn die das so machen, dann sollte das richtig sein. Desweiteren habe ich das auch in einigen anderen Tutorials gesehen. Von C/C++ weiß ich, dass man sowas nicht machen soll.

Code: Alles auswählen

import queue
import threading
import time

def worker(queue,lock):

    global finished_items
    while True:
    # do something with the element of the queue q
        try:
            lock.acquire()
            element=queue.get(False)
            lock.release()
        except queue.Empty:
            break
        finally:
            lock.release()

        finished_items +=1
        time.sleep(1)

def log(queue):
    global finished_items
    while True:
        time.sleep(5)
        print("Finished items: %s" % str(finished_items))
        if queue.empty():
            break

if __name__ == "__main__":
    lock = threading.Lock()
    finished_items=0
    worker_queue=queue.Queue()

    for i in range(100000):
        worker_queue.put(i)

    for i in range(5):
        worker_thread=threading.Thread(target=worker,args=(worker_queue,lock))
        worker_thread.start()

    log_thread = threading.Thread(target=log, args=(worker_queue,) )
    log_thread.start()
    log_thread.join()

Re: Lock für einen globalen Zähler nötig?

Verfasst: Freitag 17. Juli 2020, 09:15
von __deets__
Ich habe dir schon gesagt, dass der Zugriff auf die queue NICHT mit einem Lock geschützt wird. Die queue hat alle dafür notwendigen Mechanismen selbst an Bord. Im Gegenteil, du läufst Gefahr durch das extra lock ein deadlock zu erzeugen.

Und so wie du das jetzt gebastelt hast, kommt genau das, was du in diesem Thema angesprochen hast, als Fehler vor: du denkst, += wäre atomar. Ist es nicht. Wurde auch schon erwähnt. Und genau darum bräuchte es ein lock.

Das Minimalbeispiele globale Variablen nutzen ist kein Grund, das in nicht-Trivialem Code auch zu machen. Man benutzte keine globalen Variablen. Das ist seit 50 Jahren so.

Re: Lock für einen globalen Zähler nötig?

Verfasst: Freitag 17. Juli 2020, 09:22
von Sirius3
@ppp: finished_items ist jetzt immer noch global.

Locks (wenn man sie wirklich braucht) benutzt man immer mit dem with-Statement.

Hier ist eine Lösung eines Counters, natürlich mit threading statt multiprocessing.
https://stackoverflow.com/questions/350 ... r-35088457