生成进程时,Lock 是否有不同的 id?

When spawn processes, Does Lock have different id?

我正在尝试弄清楚 Lock 在幕后是如何工作的。我 运行 MacOS 上的这段代码使用“spawn”作为启动新进程的默认方法。

from multiprocessing import Process, Lock, set_start_method
from time import sleep


def f(lock, i):
    lock.acquire()
    print(id(lock))
    try:
        print('hello world', i)
        sleep(3)
    finally:
        lock.release()

if __name__ == '__main__':
    # set_start_method("fork")
    lock = Lock()
    for num in range(3):
        p = Process(target=f, args=(lock, num))
        p.start()
        p.join()

输出:

140580736370432
hello world 0
140251759281920
hello world 1
140398066042624
hello world 2

Lock 在我的代码中有效。但是,锁的标识让我感到困惑。由于 id 不同,它们仍然是同一把锁还是有多把锁并且它们以某种方式秘密通信? id() 是否还在 multiprocessing 中占据一席之地,我引用“CPython 实现细节:id 是对象在内存中的地址。”?

如果我使用“fork”方法,set_start_method("fork"),它会打印出相同的 id,这对我来说完全有意义。

id 实现为但不要求是给定对象的内存位置。使用 fork 时,单独的进程在修改某些内容(写入时复制)之前不会获得自己的内存 space,因此内存位置不会改变,因为它“是”同一个对象。使用 spawn 时,将创建一个全新的进程,并将 __main__ 文件作为库导入到本地命名空间中,因此可以访问所有相同的函数、类 和模块级变量(没有任何修改来自 if __name__ == "__main__": 的任何结果)。然后 python 在进程(管道)之间创建一个连接,它可以在其中发送要调用的函数以及调用它的参数。通过此管道的所有内容都必须 pickle'' 然后 unpickle'。通过向操作系统询问具有特定名称的锁(在创建锁时在父进程中创建,然后使用 pickle 发送此名称),在取消 pickle 时专门重新创建锁。这就是两个锁同步的方式,因为它由操作系统控制的对象支持。 Python 然后将此锁与一些其他数据(PyObject 一样)存储在新进程的内存中。现在调用 id 将获得此结构的位置,这是不同的,因为它是由不同进程在不同内存块中创建的。

这里有一个简单的例子可以让您相信“生成的”锁仍然是同步的:

from multiprocessing import Process, Lock, set_start_method

def foo(lock):
    with lock:
        print(f'child process lock id: {id(lock)}')

if __name__ ==  "__main__":
    set_start_method("spawn")
    lock = Lock()
    print(f'parent process lock id: {id(lock)}')
    lock.acquire() #lock the lock so child has to wait
    p = Process(target=foo, args=(lock,))
    p.start()
    input('press enter to unlock the lock')
    lock.release()
    p.join()

不同的“id”是不同的 PyObject 位置,但与底层互斥量关系不大。我不知道有一种直接的方法可以检查操作系统管理的底层锁。