「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。
在 《【Python】多进程的同步》 中,讲解了使用互斥锁和信号量来进行进程之间的同步,下面我们来解决在多进程状态下,出现找不到变量的问题。
消失的全局变量
在 《【Python】线程间同步的实现与代码》中,演示了使用多线程对全局变量进行加法操作的代码,现在我们修改一下代码,使用多进程来实现这一操作:
import multiprocessing
def add(lock):
""" num 数值每次加 1,执行 100000 次"""
global num
for _ in range(100000):
if lock.acquire():
num += 1
lock.release()
if __name__ == "__main__":
num = 0
lock = multiprocessing.Lock() # 创建一个锁
p1 = multiprocessing.Process(target= add, args=(lock, ))
p2 = multiprocessing.Process(target= add, args=(lock, ))
# 开始进程
p1.start()
p2.start()
# 等待进程结束
p1.join()
p2.join()
print(num)
运行代码,我们会发现,程序报错了:NameError: name 'num' is not defined
为什么会出现这种情况呢?回忆一下操作系统里的内容:
线程是操作系统资源分配的基本单位
也就是说, num
这个变量只存在与主线程中,而新创建的 p1
和 p2
进程不能直接访问主进程的资源,所以找不到 num
这个变量。
进程之间共享对象 - 共享内存
multiprocessing
中提供了 Value
和 Array
,可以将数据存储在共享内存映射中,供全部进程访问。下面,我们将 num
这个变量的值保存在共享内存映射中:
import multiprocessing
def add(n, lock):
""" num 数值每次加 1,执行 100000 次"""
for _ in range(100000):
if lock.acquire():
n.value += 1
lock.release()
if __name__ == "__main__":
num = multiprocessing.Value('i', 0)
lock = multiprocessing.Lock() # 创建一个锁
p1 = multiprocessing.Process(target= add, args=(num, lock, ))
p2 = multiprocessing.Process(target= add, args=(num, lock, ))
# 开始进程
p1.start()
p2.start()
# 等待进程结束
p1.join()
p2.join()
print(num.value) #200000
multiprocessing.Value(typecode_or_type, *args, lock=True)
返回一个从共享内存上创建的 ctypes
对象。可以通过 value
属性访问这个对象本身。
typecode_or_type
:返回的对象类型,如i
(int
),f
(float
),d
(doble
)lock
- 默认值为
True
, 此时会新建一个递归锁用于同步对于此值的访问操作。 - 如果是
Lock
或者RLock
对象,那么这个传入的锁将会用于同步对这个值的访问操作 - 如果是
False
, 那么对这个对象的访问将没有锁保护,也就是说这个变量不是进程安全的
- 默认值为
multiprocessing.Array(typecode_or_type, size_or_initializer, * , *lock=True*)
与 Value()
相似,只不过 Value()
的第二个参数会被当作初始值,而 Array()
的第二个参数,如果是一个整数,那就会当做数组的长度,并且整个数组的内存会初始化为0。否则,会被当成一个序列用于初始化数组中的每一个元素。
由于 Value()
默认会新建一个递归锁用于同步对于此值的访问操作,所以,我们可以将 add
函数简化为:
def add(n):
""" num 数值每次加 1,执行 1000 次"""
for _ in range(1000):
with n.get_lock():
n.value += 1
进程之间共享对象 - 服务进程
除了共享内存的方式,另一种进程间共享对象的方式是使用服务进程:由 Manager()
返回的管理器对象控制一个服务进程,该进程保存Python对象并允许其他进程使用代理操作它们。
Manager()
返回的管理器支持的类型有: list
、 dict
、 Namespace
、 Lock
、 RLock
、 Semaphore
、 BoundedSemaphore
、 Condition
、 Event
、 Barrier
、 Queue
、 Value
和 Array
。
下面,我们使用 Manager()
来实现加一的操作:
import multiprocessing
def add(n, lock):
""" num 数值每次加 1,执行 1000 次"""
for _ in range(1000):
if lock.acquire():
n.value += 1
lock.release()
if __name__ == "__main__":
lock = multiprocessing.Lock() # 创建一个锁
with multiprocessing.Manager() as manager:
num = manager.Value('i', 0)
p1 = multiprocessing.Process(target= add2, args=(num, lock))
p2 = multiprocessing.Process(target= add2, args=(num, lock))
# 开始进程
p1.start()
p2.start()
# 等待进程结束
p1.join()
p2.join()
print(num.value) # 2000