-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from portugueslab/portablequeue
Addressing UNIX problems with Queue.qsize()
- Loading branch information
Showing
8 changed files
with
220 additions
and
51 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
"""The code below has been entirely taken from the following GitHub gist: | ||
https://gist.github.com/FanchenBao/d8577599c46eab1238a81857bb7277c9 | ||
""" | ||
|
||
from multiprocessing import Value, get_context, queues | ||
|
||
|
||
class SharedCounter(object): | ||
"""A synchronized shared counter. | ||
The locking done by multiprocessing.Value ensures that only a single | ||
process or thread may read or write the in-memory ctypes object. However, | ||
in order to do n += 1, Python performs a read followed by a write, so a | ||
second process may read the old value before the new one is written by the | ||
first process. The solution is to use a multiprocessing.Lock to guarantee | ||
the atomicity of the modifications to Value. | ||
This class comes almost entirely from Eli Bendersky's blog: | ||
http://eli.thegreenplace.net/2012/01/04/shared-counter-with-pythons-multiprocessing/ | ||
""" | ||
|
||
def __init__(self, n=0): | ||
self.count = Value("i", n) | ||
|
||
def increment(self, n=1): | ||
""" Increment the counter by n (default = 1) """ | ||
with self.count.get_lock(): | ||
self.count.value += n | ||
|
||
@property | ||
def value(self): | ||
""" Return the value of the counter """ | ||
return self.count.value | ||
|
||
|
||
class PortableQueue(queues.Queue): | ||
"""A portable implementation of multiprocessing.Queue. | ||
Because of multithreading / multiprocessing semantics, Queue.qsize() may | ||
raise the NotImplementedError exception on Unix platforms like Mac OS X | ||
where sem_getvalue() is not implemented. This subclass addresses this | ||
problem by using a synchronized shared counter (initialized to zero) and | ||
increasing / decreasing its value every time the put() and get() methods | ||
are called, respectively. This not only prevents NotImplementedError from | ||
being raised, but also allows us to implement a reliable version of both | ||
qsize() and empty(). | ||
""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
self.size = SharedCounter(0) | ||
super(PortableQueue, self).__init__(*args, ctx=get_context(), **kwargs) | ||
|
||
def __getstate__(self): | ||
state = super(PortableQueue, self).__getstate__() | ||
return state + (self.size,) | ||
|
||
def __setstate__(self, state): | ||
( | ||
self._ignore_epipe, | ||
self._maxsize, | ||
self._reader, | ||
self._writer, | ||
self._rlock, | ||
self._wlock, | ||
self._sem, | ||
self._opid, | ||
self.size, | ||
) = state | ||
super(PortableQueue, self)._after_fork() | ||
|
||
def put(self, *args, **kwargs): | ||
super(PortableQueue, self).put(*args, **kwargs) | ||
self.size.increment(1) | ||
|
||
def get(self, *args, **kwargs): | ||
retrived_val = super(PortableQueue, self).get(*args, **kwargs) | ||
self.size.increment(-1) | ||
return retrived_val | ||
|
||
def qsize(self): | ||
""" Reliable implementation of multiprocessing.Queue.qsize() """ | ||
return self.size.value | ||
|
||
def empty(self): | ||
""" Reliable implementation of multiprocessing.Queue.empty() """ | ||
return not self.qsize() |
This file contains 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
Empty file.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import time | ||
from multiprocessing import Process | ||
from queue import Empty | ||
|
||
from arrayqueues.portable_queue import PortableQueue | ||
|
||
|
||
class SourceProcess(Process): | ||
def __init__(self, n_elements): | ||
super().__init__() | ||
self.n_elements = n_elements | ||
self.source_queue = PortableQueue() | ||
|
||
def run(self): | ||
for i in range(self.n_elements): | ||
self.source_queue.put(1) | ||
|
||
|
||
class SinkProcess(Process): | ||
def __init__(self, source_queue): | ||
super().__init__() | ||
self.source_queue = source_queue | ||
|
||
def run(self): | ||
while True: | ||
try: | ||
_ = self.source_queue.get(timeout=0.5) | ||
except Empty: | ||
break | ||
|
||
|
||
def test_portable_queue(): | ||
N_ELEMENTS = 5 | ||
|
||
p1 = SourceProcess(N_ELEMENTS) | ||
p2 = SinkProcess(source_queue=p1.source_queue) | ||
p1.start() | ||
time.sleep(0.5) | ||
assert p1.source_queue.qsize() == N_ELEMENTS | ||
p2.start() | ||
time.sleep(0.5) | ||
assert p1.source_queue.qsize() == 0 | ||
p2.join() | ||
p1.join() |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[tool.black] | ||
target-version = ['py36', 'py37', 'py38'] | ||
skip-string-normalization = false | ||
exclude = ''' | ||
( | ||
/( | ||
\.eggs | ||
| \.git | ||
| \.hg | ||
| \.mypy_cache | ||
| \.tox | ||
| \.venv | ||
| _build | ||
| buck-out | ||
| build | ||
| dist | ||
| examples | ||
)/ | ||
) | ||
''' | ||
|
||
[tool.isort] | ||
multi_line_output = 3 | ||
include_trailing_comma = true |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
from distutils.core import setup | ||
|
||
from setuptools import find_packages | ||
|
||
with open("requirements_dev.txt") as f: | ||
|