我使用Python 2.6和多处理模块进行多线程处理。现在我想要一个同步的dict(我真正需要的唯一原子操作是值上的+ =运算符)。
我应该使用multiprocessing.sharedctypes.synchronized()调用来包装dict吗?或者是另一种方式?
答案 0 :(得分:55)
似乎有很多扶手椅建议,没有工作实例。这里列出的答案都没有建议使用多处理,这有点令人失望和令人不安。作为python爱好者,我们应该支持我们的内置库,虽然并行处理和同步从来都不是一件小事,但我相信它可以通过适当的设计变得微不足道。这在现代多核架构中变得非常重要,并且不能过分强调!也就是说,我对多处理库很不满意,因为它仍然处于初期阶段,存在很多陷阱,错误,并且面向功能编程(我讨厌)。目前,由于多处理在服务器运行时无法共享新创建的对象的严重限制,我仍然更喜欢Pyro模块(远远超过它的时间)。管理器对象的“register”类方法只会在管理器(或其服务器)启动之前实际注册一个对象。足够的喋喋不休,更多代码:
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,该SyncManager共享syncdict字典以使用多个进程,并且可以在同一台计算机上连接到客户端,或者如果在环回以外的IP地址上运行,则运行其他计算机。在这种情况下,服务器在端口5000上的环回(127.0.0.1)上运行。使用authkey参数在操作syncdict时使用安全连接。当按下任何键时,管理器将关闭。
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”,这次没有传入一个callable来检索共享的dict。然后,它使用定制的SycnManager使用端口5000上的环回IP地址(127.0.0.1)进行连接,并使用authkey建立与Server.py中启动的管理器的安全连接。它通过调用管理器上注册的callable来检索共享的dict syncdict。它会提示用户输入以下内容:
然后客户端检查密钥是否存在。如果不是,它会在syncdict上创建密钥。然后客户端进入一个“无限”循环,它通过增量更新键的值,睡眠指定的数量,并打印syncdict只重复此过程,直到发生KeyboardInterrupt(Ctrl + C)。
我希望你能像我一样享受这个相当彻底且稍微耗时的答案。我很难直接理解为什么我在多处理模块中苦苦挣扎,Pyro让它变得轻而易举,而现在由于这个答案,我已经敲了敲头。我希望这对于如何改进多处理模块的python社区是有用的,因为我相信它有很大的希望,但在它的初期缺乏可能性。尽管描述了令人烦恼的问题,但我认为这仍然是一个非常可行的替代方案并且非常简单。您也可以使用SyncManager.dict()并将其作为参数传递给Processes,就像文档显示的方式一样,它可能是一个更简单的解决方案,这取决于您的要求,这对我来说感觉不自然。
答案 1 :(得分:4)
我会专门用一个单独的程序来维护“共享字典”:只需使用例如xmlrpclib使其他进程可以使用少量代码,通过xmlrpclib公开,例如一个函数,key, increment
执行增量,一个只使用key
并返回值,具有语义详细信息(缺少键的默认值等等),具体取决于您的应用程序的需求。
然后你可以使用你喜欢的任何方法来实现shared-dict专用进程:从内存中有简单dict的单线程服务器到简单的sqlite DB等等,我建议你开始使用代码“尽可能简单”(取决于您是否需要持久性共享字典,或者持久性对您来说不是必需的),然后根据需要进行测量和优化。
答案 2 :(得分:4)
响应并发写入问题的适当解决方案。我做了很快的研究,发现this article建议使用锁定/信号量解决方案。 (http://effbot.org/zone/thread-synchronization.htm)
虽然示例不是字典上的特异性,但我很确定您可以编写基于类的包装器对象来帮助您根据这个想法使用字典。
如果我需要以线程安全的方式实现这样的东西,我可能会使用Python Semaphore解决方案。 (假设我之前的合并技术不起作用。)我认为信号量通常会因为它们的阻塞性而降低线程效率。
来自网站:
信号量是一种更先进的锁机制。信号量具有内部计数器而不是锁定标志,并且只有在超过给定数量的线程试图保持信号量时它才会阻塞。根据信号量的初始化方式,这允许多个线程同时访问相同的代码段。
semaphore = threading.BoundedSemaphore()
semaphore.acquire() # decrements the counter
... access the shared resource; work with dictionary, add item or whatever.
semaphore.release() # increments the counter
答案 3 :(得分:3)
是否有理由首先需要共享字典?您是否可以让每个线程都维护自己的字典实例,并在线程处理结束时进行合并,或者定期使用回调将各个线程字典的副本合并在一起?
我不确切知道你在做什么,所以请保持我的书面计划可能不会逐字逐句。我的建议更多的是高级设计理念。