Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

Appendix: Examples

Appendix: Examples

Tuple iterator

This example shows how an object can be made to appear as a synchronized object, usable across multiple ThreadGroups, by using the protect mechanism.

Constructing thread-safe programs with it is left as an exercise for the reader.

from threading import Lock

class SynchronizedTupleIter:

    def __init__(self, iterable):
        self.mutex = Lock()
        with self.mutex:
            self._iterator = self.mutex.protect(iter(iterable))
        self.__freeze__()

    def __iter__(self):
        return self

    def __next__(self):
        with self.mutex:
            return self._iterator.__next__()

Counter

This example shows how to create a race-free Counter. It is just to show how to use mutexes for race-free operation. An efficient shared counter would need to use additional mechanisms to avoid contention.

class MutableInt:

    def __init__(self, value):
        self.value = value

class Counter:

    def __init__(self):
        self.mutex = Lock()
        with self.mutex:
           self.number = self.mutex.protect(MutableInt(0))
        self.__freeze__()

    def value(self):
        with self.mutex:
            return self.number.value

    def increment(self):
        with self.mutex:
            self.number.value += 1

Unsafe Counter

Protection does not guarantee thread safety, it merely enforces the locking discipline. While this makes it harder to accidentally make code that is thread unsafe, it doesn’t make it impossible. In this example, the increment method is not thread safe as another thread might modify the value between the get and the set.

class MutableInt:

    def __init__(self, value):
        self.value = value

class Counter:

    def __init__(self):
        self.mutex = Lock()
        with self.mutex:
           self.number = self.mutex.protect(MutableInt(0))
        self.__freeze__()

    def value(self):
        with self.mutex:
            return self.number.value

    def set_value(self, val):
        with self.mutex:
            self.number.value = val

    def increment(self):
        val = self.value()
        self.set_value(val+1)

Bailing out instead of allowing races

For certain algorithms it may be impractical, or of little value, to additionally guard against shared inputs. This PEP allows code to bail out of an operation instead of dealing with concurrency. This may be the case for a serialization library:

def dump(mapping: dict):
    if mapping.__shareable__ is SYNCHRONIZED:
        raise ValueError("cannot cope with data races.")
    # other states are fine:
    #   LOCAL -- no concurrent accesses
    #   PROTECTED -- mutual exclusion prevents races
    #   IMMUTABLE -- no concurrent modifications
    for key, value in mapping.items():
        dump_one(key, value)

Serializing accesses to a file

Allowing multiple threads to write to the same file concurrently can only produce non-deterministic behavior. Some simple serialization mechanisms can be implemented:

class ThreadSectionedFile:

    def __init__(self, f: file):
        self._lock = Lock()
        with self._lock:
            self._file = self._lock.protect(del f)
        self._sections: dict[Thread, list[bytes]] = dict().synchronized()

    def __enter__(self):
        self._sections[threading.current_thread()] = []
        # Note that the list is thread-local, no other thread may
        # inadvertently write into it.

    def write(self, data: bytes):
        me = threading.current_thread()
        if me not in self._sections:
            raise Exception("must call __enter__")
        self._sections[me].append(data)

    def __exit__(self, t, v, tb):
        me = threading.current_thread()
        data = self._sections[me]
        del self._sections[me]
        with self._lock:
            self._file.write(f"Thread {me.name} says:\n".encode())
            for d in data:
                self._file.write(d)
            self._file.write(b"\n")