到目前为止,以下简单的代码应始终打印出“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)上进行测试。
答案 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,如果你想进一步研究它。