深入理解Python多进程与多线程:内存管理与变量同步的关键区别
Python多进程和多线程是大家都会关注的部分,因为如果很多任务之间没有相互依赖的话,顺序并不是很重要。如果采用顺序执行,肯定会造成不必要的等待,白白浪费CPU和内存。浪费,这是我们不愿意看到的。

为了解决这个问题,我们可以采用多线程或者多进程的方式(稍后我们会讲到多线程),两者是有本质区别的。就内存而言,众所周知,进程在执行时拥有独立的内存单元,而多个线程则共享内存。这是多进程和多线程的一个主要区别。
使用Value来同步不同进程中的变量
在多进程中,由于进程之间的内存是相互隔离的,因此变量不能通过直接读取的方式在多个进程之间共享。这时,就可以使用multiprocessing库中的Value来共享各个隔离进程中的变量。
下面是一个多进程的例子:
假设有一个计数器,用来记录程序经过的循环总数。每次调用count函数后,计数器都会增加20。在主程序中,使用一个循环打开10个进程,分别调用count函数。那么理想情况下,在十个进程中共享的计数器值在程序结束后应该是200。
from multiprocessing import Process, Value
import time
def count(v):
for i in range(20):
time.sleep(0.01)
v.value += 1
def main():
value = Value('i',0)
processes = [Process(target=count, args=(value,)) for i in range(10)]
for p in processes:
p.start()
for p in processes:
p.join()
print(value.value)
if __name__ == '__main__':
for i in range(10):
main()
运行这个例子会得到什么结果?
我在主程序中运行了这个程序十次,最终结果在160-180之间。总之,一直没有达到200,这是什么原因呢?
相信很多人都明白了这个问题。这是因为多处理库中的值是细粒度的。 Value中有一个ctypes类型的对象,还有一个value属性来代表内存中的实际对象。 Value 确保只有一个线程或进程同时读取或写入该值。似乎没有问题。
但是,当第一个进程加载该值时,程序无法阻止第二个进程加载旧值。两个进程都会将该值复制到自己的私有内存中,对其进行处理,然后将其写回共享值。
那么问题出在哪里呢?
最终的共享值仅收到一个增量,而不是两个。
当变量被不同进程共享时,使用Lock来锁定变量
上述问题其实可以通过一个非常简单的方法来解决。我们只需要在多处理库中调用Lock来确保同一时刻只有一个进程可以访问这个共享变量。修改后的代码如下:
from multiprocessing import Process, Value, Lock
from time import sleep
def count(x,lock):
for i in range(20):
sleep(0.01)
with lock:
x.value += 1
def main():
counter = Value('i',0)
lock = Lock()
processes = [Process(target=count,args=(counter,lock)) for i in range(10)]
for p in processes:
p.start()
for p in processes:
p.join()
print(counter.value)
if __name__ == '__main__':
for i in range(10):
main()
这样,输出结果就恒定为200。
一些补充 1.调用get_lock()函数
事实上,Value包中已经包含了锁的概念。如果调用get_lock()函数,可以自动锁定共享变量。这实际上是推荐的方式,因为它不需要同时调用两个包。修改如下:
from multiprocessing import Process, Value
from time import sleep
def count(x):
for i in range(20):
global counter # 声明全局变量
sleep(0.01)
with counter.get_lock(): # 直接调用get_lock()函数获取锁
x.value += 1
def main():
processes = [Process(target=count, args=(counter,)) for i in range(10)]
for p in processes:
p.start()
for p in processes:
p.join()
print(counter.value)
if __name__ == '__main__':
counter = Value('i', 0) # 需要把全局变量移到主程序
main()
上面的程序比较明确,最终的结果也是200。
2.使用multiprocessing.RawValue
整个multiprocessing包中刚才调用的Value和Lock也可以用multiprocessing.RawValue来代替。