为啥互斥锁没有被锁定

Posted

技术标签:

【中文标题】为啥互斥锁没有被锁定【英文标题】:Why mutex isn't locked为什么互斥锁没有被锁定 【发布时间】:2014-02-28 05:45:17 【问题描述】:

我有互斥锁的问题,无法弄清楚为什么锁定和解锁之间的一段代码在所有线程中同时运行。 这是我的线程类:

class MyThread(QtCore.QThread):
    mutex = QtCore.QMutex()
    load_message_input = QtCore.pyqtSignal()

    def __init__(self, id, window):
        super(MyThread, self).__init__()
        self.id = id
        self.window = window

    def run(self):
        print "Thread %d is started" % self.id
        self.get_captcha_value()
        print "Thread %d is finishing" % self.id

    def get_captcha_value(self):
        MyThread.mutex.lock()
        print "Thread %d locks mutex" % self.id
        self.load_message_input.connect(self.window.show_input)
        self.load_message_input.emit()
        self.window.got_message.connect(self.print_message)
        self.window.input_finished.wait(self.mutex)
        print "Thread %d unlocks mutex" % self.id
        MyThread.mutex.unlock()

    @QtCore.pyqtSlot("QString")
    def print_message(self, msg):
        print "Thread %d: %s" % (self.id, msg)

我是这样描述窗口的:

class MyDialog(QtGui.QDialog):
    got_message = QtCore.pyqtSignal("QString")

    def __init__(self, *args, **kwargs):
        super(MyDialog, self).__init__(*args, **kwargs)
        self.last_message = None

        self.setModal(True)
        self.message_label = QtGui.QLabel(u"Message")
        self.message_input = QtGui.QLineEdit()
        self.dialog_buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
        self.dialog_buttons.accepted.connect(self.accept)
        self.dialog_buttons.rejected.connect(self.reject)
        self.hbox = QtGui.QHBoxLayout()
        self.hbox.addWidget(self.message_label)
        self.hbox.addWidget(self.message_input)
        self.vbox = QtGui.QVBoxLayout()
        self.vbox.addLayout(self.hbox)
        self.vbox.addWidget(self.dialog_buttons)
        self.setLayout(self.vbox)

        self.input_finished = QtCore.QWaitCondition()

    @QtCore.pyqtSlot()
    def show_input(self):
        print "showing input"
        self.show()
        self.setModal(True)

    @QtCore.pyqtSlot()
    def on_accepted(self):
        print "emit: ", self.message_input.text()
        self.got_message.emit(self.message_input.text())
        self.input_finished.wakeAll()

这里是主线程:

import sys
app = QtGui.QApplication(sys.argv)

window = test_qdialog.MyDialog()
threads = []

for i in range(5):
    thread = MyThread(i, window)
    if not thread.isRunning():
        thread.start()
        threads.append(thread)

sys.exit(app.exec_())

输出如下所示:

Thread 0 is startedThread 1 is startedThread 4 is started


Thread 0 locks mutexThread 3 is started
Thread 2 is started

Thread 2 locks mutex
Thread 3 locks mutex
Thread 1 locks mutex
Thread 4 locks mutex
showing input
showing input
showing input
showing input
showing input

更新: 感谢 Yoann 的建议。 这是 MyThread 类代码现在的样子:

class MyThread(QtCore.QThread):
    mutex = QtCore.QMutex()
    load_message_input = QtCore.pyqtSignal()

    def __init__(self, id, window):
        super(MyThread, self).__init__()
        self.id = id
        self.window = window
        # self.mutex = QtCore.QMutex()
        self.load_message_input.connect(self.window.show_input)

    def run(self):
        print "Thread %d is started" % self.id
        self.get_captcha_value()
        print "Thread %d is finishing" % self.id

    def get_captcha_value(self):
        MyThread.mutex.lock()
        print "Thread %d locks mutex" % self.id
        self.load_message_input.emit()
        mutex2 = QtCore.QMutex()
        mutex2.lock()
        self.window.got_message.connect(self.print_message)
        self.window.input_finished.wait(mutex2)
        mutex2.unlock()
        self.window.got_message.disconnect(self.print_message)
        print "Thread %d unlocks mutex" % self.id
        MyThread.mutex.unlock()

    @QtCore.pyqtSlot("QString")
    def print_message(self, msg):
        print "Thread %d: %s" % (self.id, msg)

现在我在第一个线程完成后得到这个异常:

Traceback (most recent call last):
  File "/path/to/script/qdialog_threads.py", line 20, in run
    self.get_captcha_value()
  File "path/to/script/qdialog_threads.py", line 34, in get_captcha_value
    MyThread.mutex.unlock()
AttributeError: 'NoneType' object has no attribute 'mutex'

【问题讨论】:

【参考方案1】:

您没有在 print 调用中刷新缓冲区。另请注意,showing input 输出是在 GUI 线程中生成的,因此它可能在互斥锁被锁定后的任何时间发生 - 包括在互斥锁已解锁之后。

【讨论】:

【参考方案2】:

你的代码有几个问题

您的打印要求都搞混了

通过这种方式锁定每个打印可以很容易地修复它(with 语句是处理获取和释放锁的上下文管理器):

# Lock declaration
CONSOLELOCK = threading.Lock()

# Then for any print
with CONSOLELOCK:
    print("Anything")

正如 Kuba Ober 所说,显示输入输出是在 GUI 线程中生成的,所以无论如何它可能会以错误的顺序出现。

当你应该使用两个互斥锁时,你使用一个互斥锁

当您调用self.window.input_finished.wait(self.mutex) 时,互斥锁被释放,因此其他所有线程都会尝试获取它。第一个到达的将获取它,发出 load_message 然后等待 input_finished 并释放互斥锁,依此类推。最终,每个线程都将锁定互斥锁,发出load_message 并等待相同的条件(同一对话框的接受信号)。一种解决方案是使用第二个互斥锁,而不是在您的条件下使用。 self.window.input_finished.wait(self.mutex2).

此外,您的 on_accepted 插槽永远不会被调用,并且您永远不会断开 window.got_message 信号,因此您的线程保持活动状态。

【讨论】:

谢谢,不知道wait() 会自动释放互斥锁。但现在我有不同的问题。在第一个线程完成后,整个程序就完成了。所以我可以输入一次消息,而不是五次。我将为 MyThread 类添加更新的代码来提问。 如果你的对话框的接受槽被调用,那么它将关闭。由于没有更多打开的窗口,应用程序将自动退出。可以通过调用 setQuitOnLastWindowClosed(False) 禁用此行为

以上是关于为啥互斥锁没有被锁定的主要内容,如果未能解决你的问题,请参考以下文章

多个互斥锁策略以及为啥库不使用地址比较

如果互斥锁被锁定,安全地跳过任务

确保当前线程持有 C++11 互斥锁

25.互斥锁

一个用户崩溃时共享内存中的互斥锁?

锁定未锁定的互斥体的效率如何?互斥锁的成本是多少?