Skip to content
This repository was archived by the owner on Apr 11, 2026. It is now read-only.

Commit 5680838

Browse files
committed
feat: update ConcurrentBuffer and ConcurrentQueue for improved performance and add usage guidelines
1 parent 08aa114 commit 5680838

7 files changed

Lines changed: 37 additions & 63 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1414

1515
---
1616

17-
## [1.1.0] - 2025-03-23
17+
## [1.1.0] - 2025-03-26
1818
### Classes Added
1919
### 1. Dynaphore
2020
- A dynamic semaphore class that allows for dynamic tuning of semaphore limits.

src/thread_factory/concurrency/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from src.thread_factory.concurrency.concurrent_stack import ConcurrentStack
77
from src.thread_factory.concurrency.concurrent_buffer import ConcurrentBuffer
88

9+
910
__all__ = [
1011
"ConcurrentBag",
1112
"ConcurrentDict",

src/thread_factory/concurrency/concurrent_buffer.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,10 @@ class ConcurrentBuffer(Generic[_T]):
147147
148148
**NOTE**: This buffer is not designed for high-contention scenarios.
149149
ConcurrentQueue or ConcurrentStack Outperforms this object in high-contention scenarios.
150+
DO NOT EXCEED 20 THREADS OVERALL (for producer and consumer pattern) WHEN USING THIS OBJECT.
150151
151-
4-6 Shards have given quite good results in most scenarios with under 10 threads, it can
152-
outperform the ConcurrentQueue in some scenarios by 2x
152+
Please follow the rule of dividing the number of threads by 2 for the producer and consumer pattern
153+
to obtain shard count. For example, if you have 10 threads, you should use 5 shards.
153154
"""
154155

155156
def __init__(
@@ -183,13 +184,12 @@ def __init__(
183184

184185
def enqueue(self, item: _T) -> None:
185186
"""
186-
Adds a new item to the buffer. The item is randomly assigned to one of the internal shards.
187+
Adds a new item to the buffer. The item is to the queue with the least length.
187188
188189
Args:
189190
item (_T): The item to add.
190191
"""
191-
# Choose a random shard to enqueue the item into. This helps distribute the load.
192-
shard_idx = random.randint(0, self._num_shards - 1)
192+
shard_idx = min(range(self._num_shards), key=lambda i: self._length_array[i])
193193
shard = self._shards[shard_idx]
194194
shard.enqueue_item(item)
195195

src/thread_factory/concurrency/concurrent_queue.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def enqueue(self, item: _T) -> None:
5656
with self._lock:
5757
self._deque.append(item)
5858

59-
def dequeue(self) -> _T:
59+
def dequeue1(self) -> _T:
6060
"""
6161
Remove and return an item from the front of the queue.
6262
@@ -72,9 +72,24 @@ def dequeue(self) -> _T:
7272
raise Empty("dequeue from empty ConcurrentQueue")
7373
return self._deque.popleft()
7474
except Empty:
75-
time.sleep(0.10)
75+
#time.sleep(0.001)
7676
raise
7777

78+
def dequeue(self) -> _T:
79+
"""
80+
Remove and return an item from the front of the queue.
81+
82+
Raises:
83+
IndexError: If the queue is empty.
84+
85+
Returns:
86+
_T: The item dequeued.
87+
"""
88+
with self._lock:
89+
if not self._deque:
90+
raise Empty("dequeue from empty ConcurrentQueue")
91+
return self._deque.popleft()
92+
7893
def peek(self) -> _T:
7994
"""
8095
Return (but do not remove) the item at the front of the queue.
Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,3 @@
11
import threading
22
import cProfile
33
import pstats
4-
5-
6-
def test():
7-
print("Test begins")
8-
print("Lock Acquired")
9-
print("This is the thread ID " + str(threading.get_ident()))
10-
x = threading.RLock()
11-
x.acquire()
12-
print("Lock Released")
13-
x.release()
14-
print("Deadlock")
15-
x.acquire()
16-
x.acquire()
17-
print("Test ends")
18-
19-
test()
20-
21-
# profiler = cProfile.Profile()
22-
# profiler.enable()
23-
#
24-
# # 🔁 Run your test or logic here
25-
# my_buffer.stress_test()
26-
#
27-
# profiler.disable()
28-
#
29-
# # 📊 Print top 30 slowest functions by cumulative time
30-
# stats = pstats.Stats(profiler).sort_stats("cumtime")
31-
# stats.print_stats(30)
32-
33-
34-
35-
testing = threading.Thread(target=test)
36-
testing2 = threading.Thread(target=test)
37-
testing3 = threading.Thread(target=test)
38-
testing4 = threading.Thread(target=test)
39-
40-
41-
testing.start()
42-
testing2.start()
43-
testing3.start()
44-
testing4.start()
45-
46-
47-

tests/concurrency/test_concurrent_buffer.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -550,10 +550,10 @@ def test_threaded_concurrent_buffer_performance(self):
550550
High-performance producer/consumer test using threads and ConcurrentBuffer.
551551
Balanced consumer workload to avoid stalling.
552552
"""
553-
self.buffer = ConcurrentBuffer(4)
554-
producers = 8
555-
consumers = 8
556-
self.items_per_producer = 50_000
553+
self.buffer = ConcurrentBuffer(10)
554+
producers = 10
555+
consumers = 10
556+
self.items_per_producer = 100_000
557557
total_items = producers * self.items_per_producer
558558

559559
try:
@@ -580,7 +580,8 @@ def consumer(target):
580580
_ = self.buffer.dequeue()
581581
consumed += 1
582582
except Empty:
583-
time.sleep(0.001)
583+
pass
584+
#time.sleep(0.001)
584585

585586
threads = []
586587
for pid in range(producers):
@@ -664,9 +665,9 @@ def test_threaded_concurrent_queue_performance(self):
664665
"""
665666
from thread_factory import ConcurrentQueue
666667
q = ConcurrentQueue()
667-
producers = 8
668-
consumers = 8
669-
items_per_producer = 50_000
668+
producers = 10
669+
consumers = 10
670+
items_per_producer = 100_000
670671
total_items = producers * items_per_producer
671672

672673
try:
@@ -693,7 +694,8 @@ def consumer(target):
693694
_ = q.dequeue()
694695
consumed += 1
695696
except Empty:
696-
time.sleep(0.001)
697+
pass
698+
#time.sleep(0.001)
697699

698700
threads = []
699701
for pid in range(producers):

tests/concurrency/test_concurrent_queue.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ def test_threaded_concurrent_queue_performance(self):
520520
q = ConcurrentQueue()
521521
producers = 10
522522
consumers = 10
523-
items_per_producer = 100000
523+
items_per_producer = 100_000
524524
total_items = producers * items_per_producer
525525

526526
try:
@@ -574,7 +574,7 @@ def test_multiprocessing_queue_performance(self):
574574
queue = multiprocessing.Queue()
575575
producers = 10
576576
consumers = 10
577-
items_per_producer = 100000
577+
items_per_producer = 100_000
578578
total_items = producers * items_per_producer
579579

580580
try:

0 commit comments

Comments
 (0)