有没有办法在多个线程中使用asyncio.Queue?

时间:2015-10-01 13:58:54

标签: python python-3.x python-asyncio

我们假设我有以下代码:

import asyncio
import threading

queue = asyncio.Queue()

def threaded():
    import time
    while True:
        time.sleep(2)
        queue.put_nowait(time.time())
        print(queue.qsize())

@asyncio.coroutine
def async():
    while True:
        time = yield from queue.get()
        print(time)

loop = asyncio.get_event_loop()
asyncio.Task(async())
threading.Thread(target=threaded).start()
loop.run_forever()

此代码的问题在于async协同程序中的循环永远不会完成第一次迭代,而queue大小正在增加。

为什么会以这种方式发生这种情况,我该怎么做才能解决这个问题?

我无法摆脱单独的线程,因为在我的实际代码中,我使用单独的线程与串行设备进行通信,而我找不到使用asyncio的方法。

4 个答案:

答案 0 :(得分:20)

asyncio.Queue is not thread-safe,因此您无法直接在多个主题中使用它。相反,您可以使用janus,它是提供线程感知asyncio队列的第三方库:

import asyncio
import threading
import janus

def threaded(squeue):
    import time
    while True:
        time.sleep(2)
        squeue.put_nowait(time.time())
        print(squeue.qsize())

@asyncio.coroutine
def async(aqueue):
    while True:
        time = yield from aqueue.get()
        print(time)

loop = asyncio.get_event_loop()
queue = janus.Queue(loop=loop)
asyncio.Task(asyncio.ensure_future(queue.async_q))
threading.Thread(target=threaded, args=(queue.sync_q,)).start()
loop.run_forever()

还有aioprocessing(完全披露:我写了它),它也提供了进程安全(以及作为副作用,线程安全)的队列,但是这样做太过分了。您并未尝试使用multiprocessing

答案 1 :(得分:4)

BaseEventLoop.call_soon_threadsafe即将到来。有关详细信息,请参阅asyncio doc

只需像这样更改threaded()

def threaded():
    import time
    while True:
        time.sleep(1)
        loop.call_soon_threadsafe(queue.put_nowait, time.time())
        loop.call_soon_threadsafe(lambda: print(queue.qsize()))

这是一个示例输出:

0
1443857763.3355968
0
1443857764.3368602
0
1443857765.338082
0
1443857766.3392274
0
1443857767.3403943

答案 2 :(得分:4)

如果您不想使用其他库,可以从该线程安排协程。用以下内容替换queue.put_nowait可以正常工作。

asyncio.run_coroutine_threadsafe(queue.put(time.time()), loop)

变量loop表示主线程中的事件循环。

编辑:

你的async协程没有做任何事情的原因是这样的 事件循环从未给它机会这样做。队列对象是 不是线程安全的,如果你仔细查看cpython代码就可以找到它 这意味着put_nowait通过唤醒队列的消费者 使用事件循环的call_soon方法的未来。如果 我们可以使用call_soon_threadsafe它应该有效。专业 然而,call_sooncall_soon_threadsafe之间存在差异 call_soon_threadsafe通过调用loop._write_to_self()唤醒事件循环。所以我们自己来称呼它:

import asyncio
import threading

queue = asyncio.Queue()

def threaded():
    import time
    while True:
        time.sleep(2)
        queue.put_nowait(time.time())
        queue._loop._write_to_self()
        print(queue.qsize())

@asyncio.coroutine
def async():
    while True:
        time = yield from queue.get()
        print(time)

loop = asyncio.get_event_loop()
asyncio.Task(async())
threading.Thread(target=threaded).start()
loop.run_forever()

然后,一切都按预期工作。

至于线程安全方面 访问共享对象,asyncio.queue在引擎盖下使用 collections.deque,其中包含线程安全appendpopleft。 也许检查队列不是空的并且popleft不是原子的,但是如果 您只在一个线程(事件循环之一)中使用队列 没关系。

其他提出的解决方案,来自华佐的loop.call_soon_threadsafe 高的答案和我的asyncio.run_coroutine_threadsafe正在做 这个,唤醒事件循环。

答案 3 :(得分:0)

仅将threading.Lock与asyncio.Queue一起使用怎么办?

class ThreadSafeAsyncFuture(asyncio.Future):
    """ asyncio.Future is not thread-safe
    https://stackoverflow.com/questions/33000200/asyncio-wait-for-event-from-other-thread
    """
    def set_result(self, result):
        func = super().set_result
        call = lambda: func(result)
        self._loop.call_soon_threadsafe(call)  # Warning: self._loop is undocumented


class ThreadSafeAsyncQueue(queue.Queue):
    """ asyncio.Queue is not thread-safe, threading.Queue is not awaitable
    works only with one putter to unlimited-size queue and with several getters
    TODO: add maxsize limits
    TODO: make put corouitine
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.lock = threading.Lock()
        self.loop = asyncio.get_event_loop()
        self.waiters = []

    def put(self, item):
        with self.lock:
            if self.waiters:
                self.waiters.pop(0).set_result(item)
            else:
                super().put(item)

    async def get(self):
        with self.lock:
            if not self.empty():
                return super().get()
            else:
                fut = ThreadSafeAsyncFuture()
                self.waiters.append(fut)
        result = await fut
        return result

另请参阅-asyncio: Wait for event from other thread