October 2nd 2013

Readers-writers lock in eventlet

The problem: You want to have a critical section of code where concurrent read operations are permitted but exclusive access for write operations — multiple readers may read data in parallel but only a single writer is allowed to write data at any one time.

Here's an implementation using the eventlet library:

from eventlet import greenthread, hubs

class ReadWriteLock(object):
    """
    Usage::

        lock = ReadWriteLock()

        with lock.reader():
            do_read_ops()

        with lock.writer():
            do_write_ops()
    """

    def __init__(self):
        self.counter = 0
        self._read_waiters = set()
        self._write_waiters = set()

    def reader(self):
        return self.Reader(self)

    def writer(self):
        return self.Writer(self)

    class Base(object):
        def __init__(self, parent):
            self.parent = parent

        def _acquire(self, waiters):
            waiters.add(greenthread.getcurrent())

            try:
                while self.parent.counter != 0:
                    hubs.get_hub().switch()
            finally:
                waiters.discard(greenthread.getcurrent())

        def _exit(self):
            for waiters, fn in (
                (self.parent._read_waiters, lambda x: x >= 0),
                (self.parent._write_waiters, lambda x: x == 0),
            ):
                if not waiters:
                    continue

                hubs.get_hub().schedule_call_global(
                    0, self._release, waiters, fn,
                )

        def _release(self, waiters, fn):
            if waiters and fn(self.parent.counter):
                waiters.pop().switch()

    class Reader(Base):
        def __enter__(self):
            if self.parent.counter < 0:
                self._acquire(self.parent._read_waiters)
            self.parent.counter += 1

        def __exit__(self, *args, **kwargs):
            self.parent.counter -= 1
            self._exit()

    class Writer(Base):
        def __enter__(self):
            if self.parent.counter != 0:
                self._acquire(self.parent._write_waiters)
            self.parent.counter -= 1

        def __exit__(self, *args, **kwargs):
            self.parent.counter += 1
            self._exit()



You can subscribe to new posts via email or RSS.