Skip to content

Commit e9438c3

Browse files
authored
Merge pull request #121 from HansBug/dev/readwrite
dev(hansbug): add read-write lock
2 parents c609700 + 77bb52f commit e9438c3

8 files changed

Lines changed: 562 additions & 2 deletions

File tree

README.md

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,13 @@ The project is structured into several top-level modules, each dedicated to a sp
5959
| **`hbutils.binary`** | Offers basic IO types and utilities for structured binary file operations, often used for low-level data handling. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/binary/index.html) |
6060
| **`hbutils.collection`** | Offers advanced data structures and utilities for manipulating sequences and collections, including grouping and deduplication. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/collection/index.html) |
6161
| **`hbutils.color`** | Deals with color models (RGB, HSV, HLS) and their calculations, including parsing and conversion. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/color/index.html) |
62+
| **`hbutils.concurrent`** | Provides concurrent utilities, including a ReadWriteLock implementation for efficient shared resource access. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/concurrent/index.html) |
6263
| **`hbutils.config`** | Contains global meta information of this package. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/config/index.html) |
6364
| **`hbutils.design`** | Contains extendable implementations for common design patterns in Python, such as Singleton. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/design/index.html) |
6465
| **`hbutils.encoding`** | Provides utilities for common encoding, decoding, and cryptographic hash calculations for binary data. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/encoding/index.html) |
6566
| **`hbutils.expression`** | A flexible system for creating and composing callable functions and complex expressions with operator overloading. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/expression/index.html) |
6667
| **`hbutils.file`** | Offers useful utilities for managing file streams, including cursor position and size retrieval. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/file/index.html) |
68+
| **`hbutils.logging`** | Provides enhanced logging capabilities, such as colored output and proper multi-line message formatting. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/logging/index.html) |
6769
| **`hbutils.model`** | Provides decorators and utilities for enhancing Python classes with features like automatic field access and visual representation. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/model/index.html) |
6870
| **`hbutils.random`** | Utilities for generating random sequences, strings (e.g., random hashes), and performing random choices. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/random/index.html) |
6971
| **`hbutils.reflection`** | Provides powerful utilities for introspection and manipulation of Python objects, functions, and modules. | [API Documentation](https://hbutils.readthedocs.io/en/latest/api_doc/reflection/index.html) |
@@ -167,14 +169,82 @@ foods = ['apple', 'orange', 'pear', 'banana', 'fish']
167169
# Group by length
168170
by_len = group_by(foods, len)
169171
print(by_len)
170-
# Expected output: {5: ['apple', 'orange'], 4: ['pear', 'fish'], 6: ['banana']}
172+
# Expected output: {5: ['apple', 'orange', 'banana'], 4: ['pear', 'fish']}
171173

172174
# Group by first letter and count the items in each group
173175
by_first_letter_count = group_by(foods, lambda x: x[0], len)
174176
print(by_first_letter_count)
175177
# Expected output: {'a': 1, 'o': 1, 'p': 1, 'b': 1, 'f': 1}
176178
```
177179

180+
### `hbutils.concurrent`
181+
182+
Utilities for managing concurrent access to shared resources.
183+
184+
#### `ReadWriteLock`
185+
186+
A reader-writer lock implementation that allows multiple concurrent readers or a single exclusive writer, optimizing for
187+
read-heavy scenarios.
188+
189+
**Documentation:
190+
** [ReadWriteLock](https://hbutils.readthedocs.io/en/latest/api_doc/concurrent/readwrite.html#hbutils.concurrent.readwrite.ReadWriteLock)
191+
192+
```python
193+
import threading
194+
import time
195+
from hbutils.concurrent import ReadWriteLock
196+
197+
# Shared resource and lock
198+
shared_data = {'value': 0}
199+
rwlock = ReadWriteLock()
200+
201+
202+
def reader(thread_id):
203+
with rwlock.read_lock():
204+
# Multiple readers can enter this block concurrently
205+
print(f"Reader {thread_id} acquired read lock. Value: {shared_data['value']}")
206+
time.sleep(0.1) # Simulate read operation
207+
print(f"Reader {thread_id} released read lock.")
208+
209+
210+
def writer(thread_id, new_value):
211+
with rwlock.write_lock():
212+
# Only one writer can enter this block, and it blocks all readers/writers
213+
print(f"Writer {thread_id} acquired write lock. Updating...")
214+
time.sleep(0.2) # Simulate write operation
215+
shared_data['value'] = new_value
216+
print(f"Writer {thread_id} updated value to {new_value} and released write lock.")
217+
218+
219+
# Example usage
220+
threads = []
221+
threads.append(threading.Thread(target=reader, args=(1,)))
222+
threads.append(threading.Thread(target=reader, args=(2,)))
223+
threads.append(threading.Thread(target=writer, args=(3, 100)))
224+
threads.append(threading.Thread(target=reader, args=(4,)))
225+
threads.append(threading.Thread(target=writer, args=(5, 200)))
226+
227+
for t in threads:
228+
t.start()
229+
230+
for t in threads:
231+
t.join()
232+
233+
print(f"Final value: {shared_data['value']}")
234+
# Expected output (order may vary, but readers 1, 2, 4 should run concurrently,
235+
# and writers 3, 5 should run exclusively):
236+
# Reader 1 acquired read lock. Value: 0
237+
# Reader 2 acquired read lock. Value: 0
238+
# ... (Reader 1 and 2 release)
239+
# Writer 3 acquired write lock. Updating...
240+
# Writer 3 updated value to 100 and released write lock.
241+
# Reader 4 acquired read lock. Value: 100
242+
# ... (Reader 4 releases)
243+
# Writer 5 acquired write lock. Updating...
244+
# Writer 5 updated value to 200 and released write lock.
245+
# Final value: 200
246+
```
247+
178248
### `hbutils.design`
179249

180250
Implementations of common design patterns.
@@ -301,6 +371,50 @@ print(f"File size: {size}")
301371
# Expected output: File size: 4
302372
```
303373

374+
### `hbutils.logging`
375+
376+
Provides enhanced logging capabilities.
377+
378+
#### `ColoredFormatter`
379+
380+
A logging formatter that applies colors to log messages based on their severity level and handles proper indentation for
381+
multi-line messages.
382+
383+
**Documentation:
384+
** [ColoredFormatter](https://hbutils.readthedocs.io/en/latest/api_doc/logging/format.html#hbutils.logging.format.ColoredFormatter)
385+
386+
```python
387+
import logging
388+
import sys
389+
from hbutils.logging import ColoredFormatter
390+
391+
# Setup logger
392+
logger = logging.getLogger('my_app')
393+
logger.setLevel(logging.DEBUG)
394+
395+
# Setup handler with ColoredFormatter
396+
handler = logging.StreamHandler(sys.stdout)
397+
handler.setFormatter(ColoredFormatter(datefmt='%H:%M:%S'))
398+
logger.addHandler(handler)
399+
400+
# Test messages
401+
logger.debug("Debug message - for detailed information.")
402+
logger.info("Info message - normal operation.")
403+
logger.warning("Warning message - potential issue.")
404+
logger.error(
405+
"Error message - critical failure.\n - Detail 1: The system is down.\n - Detail 2: Check logs for more info.")
406+
logger.critical("Critical message - immediate action required.")
407+
408+
# Expected output (colors will be applied in a real terminal):
409+
# [HH:MM:SS] DEBUG my_app Debug message - for detailed information.
410+
# [HH:MM:SS] INFO my_app Info message - normal operation.
411+
# [HH:MM:SS] WARNING my_app Warning message - potential issue.
412+
# [HH:MM:SS] ERROR my_app Error message - critical failure.
413+
# - Detail 1: The system is down.
414+
# - Detail 2: Check logs for more info.
415+
# [HH:MM:SS] CRITICAL my_app Critical message - immediate action required.
416+
```
417+
304418
### `hbutils.reflection`
305419

306420
Advanced function and object introspection and manipulation.
@@ -514,4 +628,4 @@ started.
514628

515629
## License
516630

517-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
631+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
hbutils.concurrent
2+
================================
3+
4+
.. currentmodule:: hbutils.concurrent
5+
6+
.. automodule:: hbutils.concurrent
7+
8+
.. toctree::
9+
:maxdepth: 3
10+
11+
readwrite
12+
13+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
hbutils.concurrent.readwrite
2+
===========================================
3+
4+
.. currentmodule:: hbutils.concurrent.readwrite
5+
6+
.. automodule:: hbutils.concurrent.readwrite
7+
8+
9+
ReadWriteLock
10+
----------------------------------------------------------
11+
12+
.. autoclass:: ReadWriteLock
13+
:members: __init__,acquire_read,release_read,acquire_write,release_write,read_lock,write_lock
14+
15+

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Overview
2525
api_doc/binary/index
2626
api_doc/collection/index
2727
api_doc/color/index
28+
api_doc/concurrent/index
2829
api_doc/config/index
2930
api_doc/design/index
3031
api_doc/encoding/index

hbutils/concurrent/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
This module provides concurrent utilities for managing read-write locks.
3+
4+
The module exports read-write lock implementations that allow multiple concurrent readers
5+
or a single exclusive writer. This is useful for scenarios with frequent read operations
6+
and occasional write operations, enabling better performance through concurrent reads
7+
while maintaining data consistency.
8+
9+
The lock follows these rules:
10+
- Read-Read: Non-exclusive, allows concurrent access
11+
- Read-Write: Exclusive, write operations wait for all read operations to complete
12+
- Write-Write: Exclusive, write operations execute serially
13+
- Write-Read: Exclusive, read operations wait for write operations to complete
14+
"""
15+
16+
from .readwrite import *

hbutils/concurrent/readwrite.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"""
2+
This module provides a read-write lock implementation for controlling concurrent access to shared resources.
3+
4+
The ReadWriteLock class implements a reader-writer lock that allows multiple concurrent readers
5+
or a single exclusive writer, following these rules:
6+
7+
- Read-Read: Non-exclusive, allows concurrent access
8+
- Read-Write: Exclusive, write operations wait for all read operations to complete
9+
- Write-Write: Exclusive, write operations execute serially
10+
- Write-Read: Exclusive, read operations wait for write operations to complete
11+
12+
This is useful for scenarios where you have frequent read operations and occasional write operations,
13+
allowing better performance through concurrent reads while maintaining data consistency.
14+
"""
15+
16+
import threading
17+
from contextlib import contextmanager
18+
from typing import Generator, Callable
19+
20+
__all__ = [
21+
'ReadWriteLock',
22+
]
23+
24+
25+
class ReadWriteLock:
26+
"""
27+
A read-write lock implementation for controlling concurrent access to shared resources.
28+
29+
This class implements a reader-writer lock that allows multiple concurrent readers
30+
or a single exclusive writer. The lock follows these exclusion rules:
31+
32+
- Read-Read: Non-exclusive, multiple readers can access concurrently
33+
- Read-Write: Exclusive, write operations must wait for all read operations to complete
34+
- Write-Write: Exclusive, write operations execute serially
35+
- Write-Read: Exclusive, read operations must wait for write operations to complete
36+
37+
The lock is designed to optimize scenarios with frequent read operations and occasional
38+
write operations, providing better performance through concurrent reads while maintaining
39+
data consistency during writes.
40+
41+
:param lock_factory: Factory function to create lock objects, defaults to threading.Lock
42+
:type lock_factory: Callable
43+
"""
44+
45+
def __init__(self, lock_factory: Callable = threading.Lock):
46+
"""
47+
Initialize the ReadWriteLock.
48+
49+
:param lock_factory: Factory function to create lock objects, defaults to threading.Lock
50+
:type lock_factory: Callable
51+
"""
52+
# Lock to protect the reader count
53+
self._read_ready = lock_factory()
54+
# Write lock to ensure write operations are mutually exclusive
55+
self._write_ready = lock_factory()
56+
# Current number of readers
57+
self._readers = 0
58+
59+
def acquire_read(self) -> None:
60+
"""
61+
Acquire a read lock.
62+
63+
This method allows multiple threads to acquire read locks concurrently.
64+
The first reader will block any potential writers by acquiring the write lock.
65+
Subsequent readers can proceed without blocking as long as no writer is waiting.
66+
"""
67+
with self._read_ready:
68+
self._readers += 1
69+
if self._readers == 1:
70+
# First reader needs to acquire write lock to prevent write operations
71+
self._write_ready.acquire()
72+
73+
def release_read(self) -> None:
74+
"""
75+
Release a read lock.
76+
77+
This method decrements the reader count and releases the write lock
78+
when the last reader finishes, allowing pending write operations to proceed.
79+
80+
:raises RuntimeError: If there are no active readers to release
81+
"""
82+
with self._read_ready:
83+
if self._readers == 0:
84+
raise RuntimeError('Release unlocked reader lock.')
85+
self._readers -= 1
86+
if self._readers == 0:
87+
# Last reader releases write lock, allowing write operations
88+
self._write_ready.release()
89+
90+
def acquire_write(self) -> None:
91+
"""
92+
Acquire a write lock.
93+
94+
This method provides exclusive access for write operations. It will block
95+
until all current readers have finished and no other writers are active.
96+
Once acquired, no new readers or writers can proceed until the write lock is released.
97+
"""
98+
self._write_ready.acquire()
99+
100+
def release_write(self) -> None:
101+
"""
102+
Release a write lock.
103+
104+
This method releases the exclusive write lock, allowing pending readers
105+
and writers to proceed according to the lock's scheduling policy.
106+
"""
107+
self._write_ready.release()
108+
109+
@contextmanager
110+
def read_lock(self) -> Generator[None, None, None]:
111+
"""
112+
Context manager for read lock operations.
113+
114+
This method provides a convenient way to acquire and automatically release
115+
a read lock using the 'with' statement. The lock is guaranteed to be
116+
released even if an exception occurs within the context.
117+
118+
:return: Generator for context management
119+
:rtype: Generator[None, None, None]
120+
121+
Example:
122+
>>> rwlock = ReadWriteLock()
123+
>>> with rwlock.read_lock():
124+
... # Perform read operations
125+
... data = shared_resource.read()
126+
"""
127+
self.acquire_read()
128+
try:
129+
yield
130+
finally:
131+
self.release_read()
132+
133+
@contextmanager
134+
def write_lock(self) -> Generator[None, None, None]:
135+
"""
136+
Context manager for write lock operations.
137+
138+
This method provides a convenient way to acquire and automatically release
139+
a write lock using the 'with' statement. The lock is guaranteed to be
140+
released even if an exception occurs within the context.
141+
142+
:return: Generator for context management
143+
:rtype: Generator[None, None, None]
144+
145+
Example:
146+
>>> rwlock = ReadWriteLock()
147+
>>> with rwlock.write_lock():
148+
... # Perform write operations
149+
... shared_resource.write(new_data)
150+
"""
151+
self.acquire_write()
152+
try:
153+
yield
154+
finally:
155+
self.release_write()

test/concurrent/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)