【问题标题】:do I need lock to protect mutli-thread race condition in my code我是否需要锁定来保护我的代码中的多线程竞争条件
【发布时间】:2017-01-11 20:18:50
【问题描述】:

在 Windows 上使用 Python 2.7,并将使用支持真正多线程的 Jython。 sendMessage 方法用于接收来自特定客户端的消息,并且客户端可以将相同的消息发送给其他几个客户端(这是receivers 的参数,receivers 是一个列表)。方法receiveMessage用于接收来自其他客户端的特定客户端的消息。

问题是我是否需要方法sendMessagereceiveMessage 的任何锁?我认为没有必要,因为即使客户端 X 正在接收其消息,另一个客户端 Y 也可以附加到消息池以将消息传递给客户端 X。我认为对于 defaultdict/list,附加/弹出是既原子又不需要保护。

如果我错了,请随时纠正我。

from collections import defaultdict

class Foo:
    def __init__(self):
        # key: receiver client ID, value: message
        self.messagePool = defaultdict(list)
    def sendMessage(self, receivers, message):
        # check valid for receivers
        for r in receivers:
            self.messagePool[r].append(message)
    def receiveMessage(self, clientID):
        result = []
        while len(self.messagePool[clientID]) > 0:
            result.append(self.messagePool[clientID].pop(0))

        return result

【问题讨论】:

    标签: python multithreading python-2.7 jython


    【解决方案1】:

    竞争条件是指两个或多个线程同时更改某些全局状态。

    sendMessage 的代码中,您正在更改self.messagePool[r],它是一个全局对象。因此,self.messagePool[r] 应在添加新项目之前锁定。

    与您的 receiveMessage 函数相同。

    list.appendlist.pop 是武装 O(1) 和 O(1) 操作,因此它很少会导致任何竞争条件。但是,风险仍然存在。

    【讨论】:

    • 感谢 btquanto,我认为在 Python 中,list.appendlist.pop 是原子操作(对于原子,我的意思是在操作完成之前不可能进行线程上下文切换),所以我认为不需要锁,有cmets吗?
    • 好吧,我认为它们不只需要一条 CPU 指令即可完成。如果线程在两个不同的 CPU 上运行,它们可能(尽管不太可能)同时运行。例如,list.pop 所做的是获取列表的最后一个元素,即更改列表的最终索引。想象一下两个线程同时弹出最后一个元素,然后两次更改最终索引。
    • 感谢 btquanto,为您的回复投票。我通读了这个文档,effbot.org/zone/thread-synchronization.htm,如果我从 s 列表中添加/删除元素,似乎不需要添加锁?
    【解决方案2】:

    我建议使用Queue 而不是list。它是为在线程中追加\弹出而设计的。

    【讨论】:

    • 谢谢 eri,我接受你的建议。你有没有关于 Python 列表的官方文档,它确实不是“设计用于在带有锁定的踏板中追加\弹出”
    • 感谢 eri,为您的回复投票。我通读了这个文档,effbot.org/zone/thread-synchronization.htm,如果我从 s 列表中添加/删除元素,似乎不需要添加锁?
    • 我只是说Queue是队列实现的最佳选择。
    • 谢谢eri,我完全理解队列更好,我的例子有点误导不使用队列,但整个目的不是队列v.s。列表。 :) 我的例子是关于 defaultdict 和 list。我可能在这里没有使用正确的数据结构,但是我要求 defaultdict 和 list,如果您在我的原始代码中有任何 cmets,那就太好了。
    • 您的代码不需要锁定。还有另一个答案破坏了这种观点。但我认为我的回答对将来会在谷歌上搜索的人很有用......
    【解决方案3】:

    我认为 CPython herehere 已经很好地回答了这个问题(基本上,由于 GIL,你是安全的,尽管文档中没有任何内容(如 defaultdictlist)正式说明那)。但我理解您对 Jython 的担忧,所以让我们使用一些官方资源来解决它,例如 Jython source code。 pythonic listjavaish PyList 有这种代码:

    public void append(PyObject o) {
        list_append(o);
    }
    final synchronized void list_append(PyObject o) {
    ...
    }
    public PyObject pop(int n) {
        return list_pop(n);
    }
    final synchronized PyObject list_pop(int n) {
    ...
    }
    

    由于我们有这些方法synchronized,我们可以确定列表追加和弹出在 Jython 中也是线程安全的。因此,您的代码似乎是安全的线程。

    虽然Queue 的建议仍然有效,但它确实更适合这个用例。

    【讨论】:

    • 感谢 Roman,为您的回复投票。最后一个问题是,如果我使用 Jython 进行列表添加/删除、字典添加/删除和队列推送/弹出等操作,您认为避免使用锁是否安全?
    • @LinMa:嗯,是同一个来源。 List add/remove 是同步的,字典 use javaish ConcurrentHashMap 内部是线程安全的,pythonic Queue 被 Python documentation 保证是线程安全的,Jython implements it 正确使用锁。
    • 感谢 Roman,看起来一切(我的意思是 Jython 中容器上的单个操作)都是线程安全的?为您的回复投票。
    • 感谢罗马人的耐心回答,将您的回复标记为答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    相关资源
    最近更新 更多