2017-08-26 39 views
0

以下简单的代码应该,据我所知,总是打印出'0'。但是,使用“lock = True”运行时,通常会打印出其他正数或负数。Python多处理共享变量不稳定的行为

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) 

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

澄清:当锁定设置为“False”时,它的行为与预期的一样,打印出'0',但是当它为'True'时通常不会。

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

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

+0

你使用的是什么版本的python平台?这两种情况都像预期的那样:https://ideone.com/JoBXwZ –

+0

这是从您发布的链接输出:开始FUNC2 结束FUNC2 开始FUNC1 结束FUNC1 124.0 如果最终数量不为0?这是如何预期的? – MHH

+0

我已经运行了大约10次,最后总是得到0.0。我们必须生活在一个平行的宇宙中。 :D –

回答

0

docsmultiprocessing.Value都对此很明确:

操作,如+ =其中涉及读取和写入不是原子。所以,如果,例如,你想原子方式增加一个共同的价值是不够的只是做

counter.value += 1 

假设相关的锁是递归的(这是默认设置),则可以改为做

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

对于您的评论,这不是“1000增量”。这是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进程来恢复系统。如果您想进一步研究,这种处理并发性的方法称为Actor model

+0

嗯,是的,但是,最终是不是应该在经过1000次递增和1000次递减之后,不管这种情况是否回到0? – MHH

+0

另外,这只发生在参数'lock = true'而不是'lock = false'时对我没有意义。 – MHH

+0

当'lock = False'时,你可能会很幸运,因为操作速度更快(没有“锁定”步骤)。竞赛状况依然存在;你只是没有看到它,因为它显着更小。取锁是一项非常缓慢的操作。当你没有锁的时候,你的平台上的共享内存也允许实现+ =的原子增量(我必须研究源代码以查看它们是否正在使用它)。即使是真的,依靠它也是非常危险的。 –