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

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
ppp
User
Beiträge: 13
Registriert: Samstag 24. September 2016, 08:11

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()
Benutzeravatar
sparrow
User
Beiträge: 4525
Registriert: Freitag 17. April 2009, 10:28

Vergiss dass es global gibt und benutze zur Kommunikation zwischen Threads, so wie du es bereirs machst eine Queue.
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

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.
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

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.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Zusätzlich zu dem gesagten: die siehst das falsch. Da braucht es zum inkrement ein lock. Print ist? danach ja nicht kritisch.
ppp
User
Beiträge: 13
Registriert: Samstag 24. September 2016, 08:11

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()
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

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.
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

@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
Antworten