web-dev-qa-db-ja.com

複数のスレッドでasyncio.Queueを使用する方法はありますか?

私が次のコードを持っていると仮定しましょう:

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を使用してそれを行う方法が見つからないため、別のスレッドを取り除くことはできません。

14

asyncio.Queueスレッドセーフではありません なので、複数のスレッドから直接使用することはできません。代わりに、 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を使用しようとしていない場合。

24
dano

_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
_
7
Huazuo Gao

別のライブラリを使用したくない場合は、スレッドからコルーチンをスケジュールできます。 _queue.put_nowait_を次のように置き換えると正常に機能します。

_asyncio.run_coroutine_threadsafe(queue.put(time.time()), loop)
_

変数loopは、メインスレッドのイベントループを表します。

編集:

asyncコルーチンが何もしていない理由は、イベントループがコルーチンにそうする機会を決して与えないからです。キューオブジェクトはスレッドセーフではありません。cpythonコードを調べると、これは、イベントループの_put_nowait_メソッドでfutureを使用して、_call_soon_がキューのコンシューマーをウェイクアップすることを意味します。 _call_soon_threadsafe_を使用させることができれば、機能するはずです。ただし、_call_soon_と_call_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がアトミックではないかどうかをチェックすることもできますが、キューを1つのスレッド(イベントループの1つ)でのみ使用する場合は問題ありません。

他の提案された解決策、Huazuo Gaoの答えからの_loop.call_soon_threadsafe_と私の_asyncio.run_coroutine_threadsafe_はこれを行っており、イベントループを起こしています。

6
cronos

Asyncio.Queueでthreading.Lockを使用するのはどうですか?

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

参照- 非同期:他のスレッドからのイベントを待つ

0
vladimirfol