首页 > 解决方案 > 对于通过单个语句访问的变量,我是否需要 QMutex?

问题描述

本文档中,QMutex 用于保护“数字”不被多个线程同时修改。我有一个代码,其中一个线程被指示根据另一个线程设置的标志做不同的工作。

//In thread1
if(flag)
   dowork1;
else
   dowork2;

//In thread2
void setflag(bool f)
{
    flag=f;
}

我想知道是否需要 QMutex 来保护标志,即

//In thread1
mutex.lock();
if(flag)
{
   mutex.unlock();
   dowork1;
}
else
{
   mutex.unlock();
   dowork2;
}

//In thread2
void setflag(bool f)
{
    mutex.lock();
    flag=f;
    mutex.unlock();
}

该代码与文档的不同之处在于两个线程中通过单个语句访问(读取/写入)标志,并且只有一个线程修改标志的值。

PS: 我总是在多线程编程教程中看到一个线程执行“count++”,另一个线程执行“count--”的示例,并且教程说您应该使用互斥锁来保护变量“count”。我无法理解使用互斥锁的意义。是否意味着单条语句“count++”或“count--”的执行可以在中间被打断并产生意想不到的结果?能得到什么意想不到的结果?

标签: multithreadingqtqmutex

解决方案


是否意味着单条语句“count++”或“count--”的执行可以在中间被打断并产生意想不到的结果?能得到什么意想不到的结果?

只是回答这部分:是的,执行可以在语句中间中断。

让我们想象一个简单的案例:

class A {
    void foo(){
        ++a;
    }
    int a = 0;
};

单个语句++a在汇编中被翻译为

    mov     eax, DWORD PTR [rdi]
    add     eax, 1
    mov     DWORD PTR [rdi], eax

这可以看作

eax = a;
eax += 1;
a = eax;

如果在 2 个不同线程的同一实例foo()调用(无论是在单个内核上还是在多个内核上),您无法预测程序的结果。A

它可以表现得很好:

thread 1 > eax = a  // eax in thread 1 is equal to 0
thread 1 > eax += 1 // eax in thread 1 is equal to 1
thread 1 > a = eax  // a is set to 1
thread 2 > eax = a  // eax in thread 2 is equal to 1
thread 2 > eax += 1 // eax in thread 2 is equal to 2
thread 2 > a = eax  // a is set to 2

或不:

thread 1 > eax = a  // eax in thread 1 is equal to 0
thread 2 > eax = a  // eax in thread 2 is equal to 0
thread 2 > eax += 1 // eax in thread 2 is equal to 1
thread 2 > a = eax  // a is set to 1
thread 1 > eax += 1 // eax in thread 1 is equal to 1
thread 1 > a = eax  // a is set to 1

在一个定义良好的程序中,N 次调用foo()应该导致a == N. 但是从多个线程调用foo()同一个实例会产生未定义的行为。A没有办法知道aN 次调用后的值foo()。这取决于你如何编译你的程序,使用了哪些优化标志,使用了哪个编译器,你的 CPU 的负载是多少,你的 CPU 的核心数,......

注意

class A {
public:
     bool check() const { return a == b; }
     int get_a() const { return a; }
     int get_b() const { return b; }
     void foo(){
        ++a;
         ++b;
     }
 private:
     int a = 0;
     int b = 0;
 };

现在我们有一个类,对于外部观察者来说,它始终保持a相等b。优化器可以将此类优化为:

class A {
public:
     bool check() const { return true; }
     int get_a() const { return a; }
     int get_b() const { return b; }
     void foo(){
         ++a;
         ++b;
     }
 private:
     int a = 0;
     int b = 0;
 };

因为它不会改变程序的可观察行为。

但是,如果您通过从多个线程调用 A 的同一实例上的 foo() 来调用未定义的行为,您最终可能会出现 if a = 3b = 2并且check()仍然返回true。你的代码失去了意义,程序没有做它应该做的事情,并且可以做任何事情。

从这里您可以想象更复杂的情况,例如如果 A 管理网络连接,您最终可以将客户端 #10 的数据发送到客户端 #6。如果您的程序在工厂中运行,您最终可能会激活错误的工具。

如果你想定义未定义的行为,你可以看这里:https : //en.cppreference.com/w/cpp/language/ub 和 C++ 标准为了更好地理解 UB,你可以寻找关于该主题的 CppCon 会谈.


推荐阅读