Intro
似乎有很多扶手椅建议,但没有可行的示例。这里列出的答案都没有建议使用多处理,这有点令人失望和不安。作为Python爱好者,我们应该支持我们的内置库,虽然并行处理和同步从来都不是一件小事,但我相信通过适当的设计它可以变得微不足道。这在现代多核架构中变得极其重要,怎么强调都不为过!也就是说,我对多处理库还很不满意,因为它仍处于起步阶段,存在很多陷阱、错误,并且面向函数式编程(我讨厌这一点)。目前我还是比较喜欢Pyro https://pythonhosted.org/Pyro4/由于多处理的严重限制,无法在服务器运行时共享新创建的对象,因此模块(远远领先于它的时代)超过了多处理。管理器对象的“注册”类方法只会在管理器(或其服务器)启动之前实际注册对象。废话不多说,更多代码:
服务器.py
from multiprocessing.managers import SyncManager
class MyManager(SyncManager):
pass
syncdict = {}
def get_dict():
return syncdict
if __name__ == "__main__":
MyManager.register("syncdict", get_dict)
manager = MyManager(("127.0.0.1", 5000), authkey="password")
manager.start()
raw_input("Press any key to kill server".center(50, "-"))
manager.shutdown()
在上面的代码示例中,Server.py 使用多处理的 SyncManager,它可以提供同步共享对象。该代码无法在解释器中运行,因为多处理库对于如何为每个注册对象查找“可调用”非常敏感。运行 Server.py 将启动一个自定义的 SyncManager,它共享 syncdict 字典以供多个进程使用,并且可以连接到同一台计算机上的客户端,或者如果在环回以外的 IP 地址上运行,则可以连接到其他计算机。在本例中,服务器在端口 5000 上的环回 (127.0.0.1) 上运行。在操作syncdict 时,使用 authkey 参数可以使用安全连接。当按下任意键时,管理器将关闭。
客户端.py
from multiprocessing.managers import SyncManager
import sys, time
class MyManager(SyncManager):
pass
MyManager.register("syncdict")
if __name__ == "__main__":
manager = MyManager(("127.0.0.1", 5000), authkey="password")
manager.connect()
syncdict = manager.syncdict()
print "dict = %s" % (dir(syncdict))
key = raw_input("Enter key to update: ")
inc = float(raw_input("Enter increment: "))
sleep = float(raw_input("Enter sleep time (sec): "))
try:
#if the key doesn't exist create it
if not syncdict.has_key(key):
syncdict.update([(key, 0)])
#increment key value every sleep seconds
#then print syncdict
while True:
syncdict.update([(key, syncdict.get(key) + inc)])
time.sleep(sleep)
print "%s" % (syncdict)
except KeyboardInterrupt:
print "Killed client"
客户端还必须创建一个自定义的 SyncManager,注册“syncdict”,这次无需传入可调用函数来检索共享字典。然后,它使用自定义的 SycnManager 进行连接,使用端口 5000 上的环回 IP 地址 (127.0.0.1) 以及与 Server.py 中启动的管理器建立安全连接的身份验证密钥。它通过调用管理器上注册的可调用函数来检索共享字典syncdict。它会提示用户执行以下操作:
- 要操作的syncdict中的键
- 每个周期增加键访问的值的量
- 每个周期的睡眠时间(以秒为单位)
然后客户端检查该密钥是否存在。如果没有,它会在syncdict上创建密钥。然后,客户端进入“无限”循环,其中按增量更新键的值,休眠指定的数量,并打印同步字典以重复此过程,直到发生键盘中断 (Ctrl+C)。
烦人的问题
- 管理器的注册方法必须在管理器启动之前调用,否则即使对管理器的 dir 调用将显示它确实具有已注册的方法,您也会收到异常。
- 字典的所有操作都必须使用方法而不是字典赋值来完成(syncdict["blast"] = 2 将由于多处理共享自定义对象的方式而惨败)
- 使用 SyncManager 的 dict 方法可以缓解恼人的问题 #2,但烦人的问题 #1 会阻止 SyncManager.dict() 返回的代理被注册和共享。 (SyncManager.dict() 只能在管理器启动后调用,而 register 只能在管理器启动之前工作,因此 SyncManager.dict() 仅在进行函数式编程并将代理作为参数传递给进程时有用,例如文档示例确实如此)
- 服务器和客户端都必须注册,尽管直观上看起来客户端在连接到管理器后才能弄清楚(请将其添加到您的多处理开发人员的愿望清单中)
Closing
我希望您和我一样喜欢这个相当彻底且稍微耗时的答案。我很难弄清楚为什么我在使用 Pyro 让它变得轻而易举的多处理模块上如此费力,现在多亏了这个答案,我已经一针见血了。我希望这对 python 社区如何改进多处理模块有用,因为我确实相信它有很大的前景,但在其起步阶段还没有达到可能的程度。尽管描述了烦人的问题,我认为这仍然是一个相当可行的替代方案,而且非常简单。您还可以使用 SyncManager.dict() 并将其作为参数传递给 Processes,按照文档显示的方式,这可能是一个更简单的解决方案,具体取决于您的要求,这对我来说感觉不自然。