Last active
January 15, 2025 00:16
-
-
Save tylerneylon/a7ff6017b7a1f9a506cf75aa23eacfd6 to your computer and use it in GitHub Desktop.
A simple read-write lock implementation in Python.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" rwlock.py | |
A class to implement read-write locks on top of the standard threading | |
library. | |
This is implemented with two mutexes (threading.Lock instances) as per this | |
wikipedia pseudocode: | |
https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Using_two_mutexes | |
Code written by Tyler Neylon at Unbox Research. | |
This file is public domain. | |
""" | |
# _______________________________________________________________________ | |
# Imports | |
from contextlib import contextmanager | |
from threading import Lock | |
# _______________________________________________________________________ | |
# Class | |
class RWLock(object): | |
""" RWLock class; this is meant to allow an object to be read from by | |
multiple threads, but only written to by a single thread at a time. See: | |
https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock | |
Usage: | |
from rwlock import RWLock | |
my_obj_rwlock = RWLock() | |
# When reading from my_obj: | |
with my_obj_rwlock.r_locked(): | |
do_read_only_things_with(my_obj) | |
# When writing to my_obj: | |
with my_obj_rwlock.w_locked(): | |
mutate(my_obj) | |
""" | |
def __init__(self): | |
self.w_lock = Lock() | |
self.num_r_lock = Lock() | |
self.num_r = 0 | |
# ___________________________________________________________________ | |
# Reading methods. | |
def r_acquire(self): | |
self.num_r_lock.acquire() | |
self.num_r += 1 | |
if self.num_r == 1: | |
self.w_lock.acquire() | |
self.num_r_lock.release() | |
def r_release(self): | |
assert self.num_r > 0 | |
self.num_r_lock.acquire() | |
self.num_r -= 1 | |
if self.num_r == 0: | |
self.w_lock.release() | |
self.num_r_lock.release() | |
@contextmanager | |
def r_locked(self): | |
""" This method is designed to be used via the `with` statement. """ | |
try: | |
self.r_acquire() | |
yield | |
finally: | |
self.r_release() | |
# ___________________________________________________________________ | |
# Writing methods. | |
def w_acquire(self): | |
self.w_lock.acquire() | |
def w_release(self): | |
self.w_lock.release() | |
@contextmanager | |
def w_locked(self): | |
""" This method is designed to be used via the `with` statement. """ | |
try: | |
self.w_acquire() | |
yield | |
finally: | |
self.w_release() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@tylerneylon, If you put queue on
r_acquire
only (not the whole operation), readers will not block each other. This is called "fair" order.Here is example with more detailed explanation https://en.wikipedia.org/wiki/Readers%e2%80%93writers_problem#Third_readers%E2%80%93writers_problem.