Python asyncio协议竞争条件

时间:2016-02-24 19:17:42

标签: python-3.x thread-safety multiprocessing race-condition python-asyncio

考虑以下代码(解释如下):

import asyncio

class MyClass(object):
    def __init__(self):
        self.a = 0
    def incr(self, data):
        self.a += 1
        print(self.a)

class GenProtocol(asyncio.SubprocessProtocol):
    def __init__(self, handler, exit_future):
        self.exit_future = exit_future
        self.handler = handler

    def pipe_data_received(self, fd, data):
        if fd == 1:
            self.handler(data)
        else:
            print('An error occurred')

    def process_exited(self):
        self.exit_future.set_result(True)

def start_proc(stdout_handler, *command):
    loop = asyncio.get_event_loop()
    exit_f = asyncio.Future(loop=loop)
    subpr = loop.subprocess_exec(lambda: GenProtocol(stdout_handler, exit_f),
                                 *command,
                                 stdin=None)
    transport, protocol = loop.run_until_complete(subpr)

    @asyncio.coroutine
    def waiter(exit_future):
        yield from exit_future

    return waiter, exit_f

def main():
    my_instance = MyClass()
    loop = asyncio.get_event_loop()
    waiter, exit_f = start_proc(my_instance.incr, 'bash', 'myscript.sh')
    loop.run_until_complete(waiter(exit_f))
    loop.close()

if __name__ == '__main__':
    main()

组件的简要说明如下:

  1. MyClass非常简单
  2. GenProtocol是一个允许为子进程接收的数据指定自定义处理程序的类。标准输出。
  3. start_proc允许您通过GenProtocol
  4. 启动自定义流程,为stdout上收到的数据指定自定义处理程序
  5. my_proc是一个永久运行的过程,在任意时间向管道发送数据
  6. 现在我的问题如下:因为我使用方法作为处理程序,并且因为该方法以非原子方式改变实例属性,这是否有潜在危险?例如,当我在子进程上异步接收数据时, pipe,是同时调用两次的处理程序(因此冒着破坏MyClass.a中的数据的风​​险)或者它是否被序列化(即第二次调用处理程序直到第一次执行才执行)?

1 个答案:

答案 0 :(得分:4)

协议方法是常规函数,而不是协同程序。 他们内部没有屈服点

因此执行顺序非常简单:所有调用都是序列化的,竞争条件是不可能的。

<强> UPD

在示例中,pipe_data_received()不是协程,而只是一个内部没有await / yield from的函数。

asyncio总是立即执行整个操作,中间没有任何上下文切换。

您可能认为pipe_data_received()受到锁定保护,但实际上并不需要任何锁定。

当你有这样的协程时,锁是强制性的:

async def incr(self):
    await asyncio.sleep(0.1)
    self.counter +=1

后者incr()是一个协程,而且,在sleep()调用时很有可能进行上下文切换。如果您想保护并行递增,可以使用asyncio.Lock()

def __init__(self):
    self.counter = 0
    self.lock = asyncio.Lock()

async def incr(self):
    async with self._lock:
        await asyncio.sleep(0.1)
        self.counter +=1