Einen Zähler threadsicher inkrementieren und zurückgeben

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
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hallo,

ich habe mal wieder eine recht allgemeine Frage, die ich für verschiedene Skripte/Programme benötige.

Es kommt hin und wieder vor, dass ich eine neue Instanz einer laufenden Nummer benötige. Ohne Threads geht das folgendermaßen problemlos:

Code: Alles auswählen

class Lnr:
    iLaufendeNummer = 0
    def next(self):
        iNextId = Lnr.iLaufendeNummer
        Lnr.iLaufendeNummer += 1
        return iNextId

iNext = Lnr.next()
Sobald aber mehrere Threads ins Spiel kommen, besteht die Gefahr, dass der bearbeitete Prozess zwischen Nummerholen und Nummerinkrementieren wechselt und die Nummer zwei mal geholt wird.
Ideal wäre es, wenn eine Funktion wie "i+=1" i um ein erhöhen und gleichzeitig den neuen Wert zurückgeben würde. Ansonsten fällt mir nur ein, dass man einen lock verwendet. Was passiert eigentlich, wenn mehrere Threads gleichzeitig auf dieselbe! Funktion zugreifen?

Ist mein Problem ein Standardfall der Informatik?

Grüße,
Michael
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Michael Schneider hat geschrieben:laufenden Nummer
Hallo Michael!

Wegen dem Zähler. Den brauchst du nicht mehr extra implementieren. Den gibt es schon:

Code: Alles auswählen

>>> import itertools
>>> counter = itertools.count()
>>> counter.next()
0
>>> counter.next()
1
>>> counter.next()
2
>>> 
Ob das Ganze auch threadsafe ist, weiß ich noch nicht. Mal sehen ob ich etwas aus den Quellen raus lesen kann.

EDIT:

Ich konnte im Quellcode keinen Hinweis auf einen Lock entdecken. Allerdings weiß ich nicht, wo die Grenzen beim Threading in Python sind und ob da intern nicht von GIL eine Grenze gezogen wird. Hier ist eine Aussage eines Fachmannes gefragt.

Bis es so weit ist -- hier ein Vorschlag:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import itertools
import threading


class Counter(object):
    
    def __init__(self):
        self._count = itertools.count()
        self._lock = threading.Lock()
    
    
    def next(self):
        self._lock.acquire()
        try:
            return self._count.next()
        finally:
            self._lock.release()


def main():
    counter = Counter()
    print counter.next()
    print counter.next()
    print counter.next()


if __name__ == "__main__":
    main()
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
lunar

Aus der itertools-Dokumentation:
New in version 2.3.
Da bleibt Michael Schneider leider außen vor ;) Hätte allerdings imho auch nichts geholfen, da die Implementierung für mich mit meinen beschränkten C-Kenntnissen nicht thread-sicher aussieht.

Ich würde ein einfach Lock verwenden, um den Zugriff auf den Counter abzusichern, so dieser über verschiedene Threads verwendet wird.

Edit: Zu langsam. Während ich noch tippe, hat gerold mal wieder ein komplettes Snippet auf die Beine gestellt ;)
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Nachdem ja immer nur ein Thread überhaupt gleichzeitig Python-Code ausführen kann, und in der Implementierung von count() kein Py_BEGIN_ALLOW_THREADS vorkommt, halte ich das durchaus für threadsicher.
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
lunar

birkenfeld hat geschrieben:Nachdem ja immer nur ein Thread überhaupt gleichzeitig Python-Code ausführen kann, und in der Implementierung von count() kein Py_BEGIN_ALLOW_THREADS vorkommt, halte ich das durchaus für threadsicher.
Wieder was gelehrt.
BlackJack

Gilt natürlich nur solange man CPython benutzt und es dort das GIL gibt.
Antworten