Python多进程信号量不起作用

5
我希望我的程序一次只打印出一行,但是它却一次性打印多行,结果显得混乱。我无法确定为什么信号量没有阻止多个进程相互干扰地打印。
如何使它尊重信号量?
这是一个简化后的代码版本,运行时也会遇到同样的问题(我正在Windows上使用Python 2.7.11(无法更改)):
import multiprocessing

rightofway = multiprocessing.Semaphore(1)

def writenum(number):
    rightofway.acquire()
    print("[+] - " + str(number))
    rightofway.release()
    return

def main():
    starting = 0
    ending = 50

    list = range(starting, ending)

    pool = multiprocessing.Pool(10)
    pool.map(writenum, list)
    return

#Required for Windows multiprocessing
if __name__ == '__main__':
    main()

这里是混乱输出的示例:

[+] - 0
[+] - 1
[+] - 2
[+] - 3
[+] - 4
[+] - 5
[+] - 6
[+] - 7
[[+] - 8
+] - 10[
+] - 9[+] - 11
[+] - 12

[[+] - 13+] - 14

[[+] - 15+] - 16

[[+] - 18+] - 17

[[+] - 19+] - 20

[[+] - 22+] - 21

[[+] - 23+] - 24

[[+] - 26+] - 25

[[+] - 27+] - 28

[[+] - 30+] - 29

[[+] - 31+] - 32

[[+] - 34+] - 33

[[+] - 35+] - 36

[[+] - 38+] - 37

[[+] - 39+] - 40

[[+] - 42+] - 41

[[+] - 43+] - 44

[[+] - 46+] - 45

[[+] - 47+] - 48

[+] - 49

以下是我想要的输出示例(注意我不关心顺序):

[+] - 0
[+] - 1
[+] - 2
[+] - 3
[+] - 4
[+] - 5
[+] - 6
[+] - 7
[+] - 8
[+] - 9
[+] - 10
[+] - 11
[+] - 12
[+] - 13
[+] - 14
[+] - 15
[+] - 16
[+] - 17
[+] - 18
[+] - 19
[+] - 20
[+] - 21
[+] - 22
[+] - 23
[+] - 24
[+] - 25
[+] - 26
[+] - 27
[+] - 28
[+] - 29
[+] - 30
[+] - 31
[+] - 32
[+] - 33
[+] - 36
[+] - 34
[+] - 35
[+] - 37
[+] - 38
[+] - 40
[+] - 39
[+] - 41
[+] - 42
[+] - 44
[+] - 43
[+] - 45
[+] - 46
[+] - 48
[+] - 47
[+] - 49

你使用的是哪个操作系统? - tdelaney
@tdelaney - Windows。我会把这个加到我的问题中。 - Danegraphics
1
我手头没有Windows,但这个被接受的解决方案使用了一个带有池的初始化器。这是我对修复的猜测。一个锁应该适合你,或者可以改为信号量。https://dev59.com/lV4b5IYBdhLWcg3wzUfa?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa - tdelaney
@tdelaney - 这段代码对你有效吗? - Danegraphics
@tdelaney - 啊,好的,谢谢你的尝试。 - Danegraphics
显示剩余3条评论
2个回答

5
你的问题类似于这个
根据多进程编程指南:
明确地将资源传递给子进程。更好的方式是将对象作为参数传递给子进程的构造函数。
除了使代码(潜在地)与Windows兼容之外,没有其他原因。
在Windows上,您需要将共享对象传递给Process构造函数的参数列表。否则,子进程将获得一个全新的副本,而不是父进程的副本。这就是为什么你会觉得Semaphore不起作用的原因。这两个进程正在创建自己独立的Semaphore对象,而不是共享同一个对象。
要将Semaphore对象传递给Windows上的Pool,您需要付出一些努力,但不需要太多。由于无法直接将Semaphore对象传递给writenum函数,因此需要依赖于Pool初始化器。
from multiprocessing import Semaphore, Pool

mutex = None

def initializer(semaphore):
    """This function is run at the Pool startup. 
    Use it to set your Semaphore object in the child process.

    """
    global mutex

    mutex = semaphore

def writenum(args):
    with mutex:
        print "[+] - " + str(number)

def main():
    semaphore = Semaphore()
    pool = Pool(initializer=initializer, initargs=[semaphore])

    numbers = range(50)

    pool.map(writenum, numbers)

编辑:刚刚注意到我写的是Lock而不是Semaphore。核心原理仍然相同。


成功了!我想我知道为什么代码是那样的,但还是问一下。为什么我们需要在顶部有mutex=None,即使它在初始化函数中声明了?另外,为什么初始化函数不是Pool对象的一部分? - Danegraphics
你能更好地解释一下,“为什么初始化函数不是Pool对象的一部分”这个问题的意思吗? - noxdafox
看起来 Pool() 是为了将参数带入进程的全局空间而构建的。如果是这样,那么为什么我不可以直接将(已经是全局的?)信号量传递给进程,而必须自己编写部分功能呢? - Danegraphics
“Pool”有两种向子进程传递数据的方式。第一种是通过“apply”/“map”函数参数传递。这需要参数可被pickle化,而“Semaphores”则不行。你只能依赖第二种方式,即使用“initializer”函数。由于该函数无法返回值,因此您的数据唯一持久存在的方法是使用全局变量。 - noxdafox

0
为了让事情变得更容易,以下方法适用于我。在Win10上测试过。 简而言之 - 使用锁而不是信号量
import multiprocessing

rightofway = multiprocessing.Lock()

def writenum(number):

    with rightofway:
        print("[+] - " + str(number))

    return

def main():
    starting = 0
    ending = 50

    list = range(starting, ending)

    pool = multiprocessing.Pool(10)
    pool.map(writenum, list)
    return

#Required for Windows multiprocessing
if __name__ == '__main__':
    main()

1
由于某些原因,这对我来说不起作用。结果仍然混乱无序。 - Danegraphics

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接