Python多处理共享变量不稳定的行为

时间:2017-08-26 17:09:29

标签: python python-multiprocessing

到目前为止,以下简单的代码应始终打印出“0”。但是,当使用“lock = True”运行时,它通常会打印出其他正数或负数。

  import multiprocessing as mp
  import sys
  import time

  num = mp.Value('d', 0.0, lock = False)


  def func1():
      global num
      print ('start func1')
      #While num.value < 100000:
      for x in range(1000):
          num.value += 1
          #print(num.value)
      print ('end func1')

  def func2():
      global num
      print ('start func2')
      #while num.value > -10000:
      for x in range(1000):
          num.value -= 1
          #print(num.value)
      print ('end func2')

if __name__=='__main__':
    ctx = mp.get_context('fork')
    p1 =  ctx.Process(target=func1)
    p1.start()
    p2 = ctx.Process(target=func2)
    p2.start()
    p1.join()
    p2.join()
    sys.stdout.flush()
    time.sleep(25)
    print(num.value)

有人可以提供任何解释吗?

澄清:当lock设置为“False”时,它会按预期运行,打印输出“0”,但是当它为“True”时,它通常不会。

对于较大的“范围”值,这更常见/更常见。

使用python 3.6在两个平台(Mac OSx和Ubuntu 14.04.01)上进行测试。

1 个答案:

答案 0 :(得分:0)

multiprocessing.Value的{​​{3}}对此非常明确:

  

像+ =这样涉及读写的操作不是原子的。因此,例如,如果您希望以原子方式递增共享值,则仅执行

是不够的
counter.value += 1
     

假设关联的锁是递归的(默认情况下是这样),你可以改为

with counter.get_lock():
    counter.value += 1

对于你的评论,这不是&#34; 1000次递增&#34;。这是1000次迭代:

# Take lock on num.value
temp_value = num.value    # (1)
# release lock on num.value (anything can modify it now)
temp_value += 1           # (2)
# Take lock on num.value
num.value = temp_value    # (3)
# release lock on num.value

当它说+=不是原子时,它意味着什么。

如果在第2行期间其他进程修改了num.value,则第3行会将错误的值写入num.value

举一个更好的方法来处理你正在做的事情的例子,这里有一个使用队列的版本,确保一切都在锁定步骤中保持定时:

import multiprocessing as mp
import queue
import sys


# An increment process. Takes a value, increments it, passes it along
def func1(in_queue: mp.Queue, out_queue: mp.Queue):
    print('start func1')

    for x in range(1000):
        n = in_queue.get()
        n += 1
        print("inc", n)
        out_queue.put(n)
    print('end func1')


# An decrement process. Takes a value, decrements it, passes it along
def func2(in_queue: mp.Queue, out_queue: mp.Queue):
    print('start func2')
    for x in range(1000):
        n = in_queue.get()
        n -= 1
        print("dec", n)
        out_queue.put(n)
    print('end func2')


if __name__ == '__main__':
    ctx = mp.get_context('fork')

    queue1 = mp.Queue()
    queue2 = mp.Queue()

    # Make two processes and tie their queues back to back. They hand a value
    # back and forth until they've run their course.
    p1 = ctx.Process(target=func1, args=(queue1, queue2,))
    p1.start()
    p2 = ctx.Process(target=func2, args=(queue2, queue1,))
    p2.start()

    # Get it started
    queue1.put(0)

    # Wait from them to finish
    p1.join()
    p2.join()

    # Since this is a looping process, the result is on the queue we put() to.
    # (Using block=False because I'd rather throw an exception if something
    # went wrong rather than deadlock.)
    num = queue1.get(block=False)

    print("FINAL=%d" % num)

这是一个非常简单的例子。在更健壮的代码中,您需要考虑在失败情况下会发生什么。例如,如果p1抛出异常,p2将死锁等待其值。在许多方面,这是一件好事,因为这意味着您可以通过启动具有相同队列的新p1进程来恢复系统。这种处理并发的方法称为docs,如果你想进一步研究它。