是否必须使用互斥锁来访问其他线程的外部变量?

时间:2016-03-29 14:25:14

标签: c++ multithreading qt mutex

我正在用Qt / C ++开发一个应用程序。在某些时候,有两个线程:一个是UI线程,另一个是后台线程。我必须根据类型为bool的extern变量的值从后台线程执行一些操作。我通过单击UI上的按钮来设置此值。

header.cpp

extern bool globalVar;

mainWindow.cpp

//main ui thread on button click
setVale(bool val){
 globalVar = val;
}

backgroundThread.cpp

 while(1){
  if(globalVar)
    //do some operation
  else
    //do some other operation
  }

此处,只有当用户点击按钮时才会写入globalVar,而读取会持续发生。

所以我的问题是:

  1. 在上述情况下,互斥是强制性的吗?
  2. 如果同时发生读写操作,是否会导致应用程序崩溃?
  3. 如果读取和写入同时发生,globalVar是否会有truefalse以外的其他值?
  4. 最后,操作系统是否提供任何类型的锁定机制来阻止读/写操作同时通过不同的线程访问内存位置?

4 个答案:

答案 0 :(得分:3)

因为它只是一个普通的bool,我会说互斥量过大,你应该选择一个原子整数。原子将在一个CPU时钟中读写,所以不用担心,它将是无锁的,如果可能的话,它总是更好。

如果它更复杂,那么请务必使用互斥锁。

它不会因此而崩溃,但您可能会受到数据损坏,这可能会导致应用程序崩溃。

系统不会为您管理这些内容,您可以手动执行,只需确保对数据的所有访问都通过互斥锁。

编辑:

由于您指定了多次不需要复杂解决方案的次数,因此您可以选择仅使用互斥锁而不是bool。没有必要用互斥锁保护bool,因为你可以使用互斥锁作为bool,是的,你可以使用原子,但这就是互斥锁已经做的(在递归互斥锁的情况下还有一些额外的功能) )。

您的确切工作量也很重要,因为您的示例在实践中没有多大意义。知道那些some operation是什么会很有帮助。

因此,在您的ui主题中,您可以简单地val ? mutex.lock() : mutex.unlock(),在您的辅助主题中,您可以使用if (mutex.tryLock()) doStuff; mutex.unlock(); else doOtherStuff;。现在,如果辅助线程中的操作花费的时间太长而您正在更改主线程中的锁定,那么将阻止主线程直到辅助线程解锁。您可以在主线程中使用tryLock(timeout),根据您的喜好,lock()将阻止直到成功,而tryLock(timeout)将阻止阻止,但锁定可能会失败。另外,请注意不要从锁定的线程以外的线程中解锁,也不要解锁已经解锁的互斥锁。

根据您实际执行的操作,可能采用异步事件驱动方法更为合适。你真的需要while(1)吗?你多久进行一次这些操作?

答案 1 :(得分:3)

循环

while(1){
   if(globalVar)
      //do some operation
   else
      //do some other operation
}

busy waiting,非常浪费。因此,你可能会更好地使用一些经典的同步来唤醒后台线程(主要是)当有什么事情要做时。您应该考虑调整this example of std::condition_variable

说你开始:

#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex m;
std::condition_variable cv;
bool ready = false;

您的工作线程可以是这样的:

void worker_thread()
{
    while(true)
    {
        // Wait until main() sends data
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return ready;});

        ready = false;

        lk.unlock();
}

通知线程应该这样做:

{
    std::lock_guard<std::mutex> lk(m);
    ready = true;
}
cv.notify_one();

答案 2 :(得分:1)

  

在上述情况下,互斥是必要的吗?

互斥是一种可行的工具。你真正需要的是三件事:

  1. 确保原子更新的一种方法(bool会给你这个,因为它被强制要求按照标准的整体类型)

  2. 一种确保一个线程写入的效果在另一个线程中实际可见的方法。这听起来可能违反直觉,但c ++内存模型是单线程的,优化(软件和硬件)不需要考虑跨线程通信,而且......

  3. 一种阻止编译器(和CPU !!)重新排序读写的方法。

  4. 隐含问题的答案是'是'。在所有这些事情上你都需要做些什么(见下文)

      

    如果同时发生读写操作会导致应用程序崩溃吗?

    不是当它是一个bool,但程序将不会像你期望的那样。事实上,因为程序现在表现出未定义的行为,所以你根本不能再对其行为进行推理。

      

    如果读取和写入同时发生,globalVar是否会有除了true或false之外的某些值?

    不是在这种情况下,因为它是一种内在的(原子)类型。

      

    是否会发生不同线程同时访问(读/写)内存位置,OS是否提供任何类型的锁定机制来阻止它?

    除非您指定一个。

    您的选择是:

    • std::atomic<bool>
    • std::mutex
    • std::atomic_signal_fence

答案 3 :(得分:1)

实际上,只要您使用整数类型(不是bool),将其设为volatile,并通过正确对齐其存储空间来保留其自己的缓存行,您不需要做任何特别的事。

  

在上述情况下,互斥是必要的吗?

仅当您希望保持变量的值与其他状态同步时。

  

如果读取和写入同时发生,这会导致应用程序崩溃吗?

根据C ++标准,它是未定义的行为。所以任何事情都可能发生:您的应用程序可能不会崩溃,但其状态可能会被巧妙地破坏。但在现实生活中,编译器通常会提供一些理智的实现定义行为,除非你的平台真的很奇怪,否则你会很好。任何普通的东西,比如32位和64位的英特尔,PPC和ARM都可以。

  

如果读取和写入同时发生,globalVar是否会有除了true或false之外的某些值?

globalVar只能有这两个值,所以除非你在谈论它的二进制表示,否则说任何其他值是没有意义的。是的,可能会发生二进制表示不正确而不是编译器所期望的。这就是为什么你不应该使用bool而是使用uint8_t

我不喜欢在代码审查中看到这样的标志,但如果uint8_t标志是解决您正在解决的任何问题的最简单的解决方案,我会说它。 if (globalVar)测试将零视为false,其他任何视为true,因此临时“乱码”是可以的,并且在实践中不会产生任何奇怪的效果。根据标准,当然,你将面临未定义的行为。

  

是否会发生不同线程同时访问(读/写)内存位置,OS是否提供任何类型的锁定机制来阻止它?

这不是操作系统的工作。

说到练习,但是:在任何合理的平台上,使用std::atomic_bool都不会比使用裸uint8_t有任何开销,所以只需使用它就可以完成。