Seite 1 von 1

Einen Zähler threadsicher inkrementieren und zurückgeben

Verfasst: Sonntag 1. Juli 2007, 11:15
von Michael Schneider
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

Re: Einen Zähler threadsicher inkrementieren und zurückgeben

Verfasst: Sonntag 1. Juli 2007, 11:32
von gerold
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
:-)

Verfasst: Sonntag 1. Juli 2007, 11:56
von 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 ;)

Verfasst: Sonntag 1. Juli 2007, 14:25
von birkenfeld
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.

Verfasst: Sonntag 1. Juli 2007, 14:34
von 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.

Verfasst: Sonntag 1. Juli 2007, 17:41
von BlackJack
Gilt natürlich nur solange man CPython benutzt und es dort das GIL gibt.