是否必须从其他线程访问 extern 变量

Is mutex mandatory to access extern variable from a different thread?

本文关键字:访问 extern 变量 线程 其他 是否      更新时间:2023-10-16

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

标头.cpp

extern bool globalVar;

主窗口.cpp

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

背景线程.cpp

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

在这里,仅当用户单击按钮时,写入globalVar才会发生,而读取是连续发生的。

所以我的问题是:

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

循环

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

着等待,这是极其浪费的。因此,您可能最好使用一些经典的同步,当有事情要做时,这些同步将唤醒后台线程(主要是(。您应该考虑改编此 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();

由于它只是一个普通布尔值,我会说互斥锁是矫枉过正的,你应该选择原子整数。原子将在单个CPU时钟中读取和写入,因此不必担心,并且它将是无锁定的,如果可能的话,这总是更好的。

如果它是更复杂的东西,那么一定要选择互斥锁。

它不会仅从这一点崩溃,但您可能会获得数据损坏,这可能会使应用程序崩溃。

系统不会为您管理这些东西,您可以手动操作,只需确保对数据的所有访问都通过互斥锁即可。

编辑:

由于您多次指定不需要复杂的解决方案,因此您可以选择简单地使用互斥锁而不是布尔值。没有必要用互斥锁保护布尔值,因为您可以将互斥锁用作布尔值,是的,你可以使用原子,但这就是互斥锁已经做的事情(在递归互斥锁的情况下加上一些额外的功能(。

您的

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

所以在你的 ui 线程中你可以简单地val ? mutex.lock() : mutex.unlock(),在你的辅助线程中你可以使用 if (mutex.tryLock()) doStuff; mutex.unlock(); else doOtherStuff; 。现在,如果辅助线程中的操作花费的时间太长,并且您碰巧正在更改主线程中的锁,这将阻塞主线程,直到辅助线程解锁。您可以在主线程中使用tryLock(timeout),具体取决于您的喜好,lock()将阻塞直到成功,而tryLock(timeout)将阻止阻塞,但锁定可能会失败。另外,请注意不要从锁定时使用的线程以外的线程解锁,也不要解锁已解锁的互斥锁。

根据你实际在做什么,也许异步事件驱动的方法会更合适。你真的需要那while(1)吗?执行这些操作的频率如何?

在上述情况下,是否需要互斥锁?

互斥锁是一种可以工作的工具。您实际需要的是三件事:

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

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

  3. 一种防止编译器(和 CPU!!(对读取和写入重新排序的方法。

隐含问题的答案是"是"。在做所有这些事情时,您将需要一些东西(见下文(

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

不是当它是布尔值时,但程序不会按您的预期运行。事实上,由于程序现在表现出未定义的行为,您根本无法推理其行为。

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

在这种情况下不是,因为它是固有(原子(类型。

它是否会发生通过不同线程同时访问(读/写(内存位置的情况,操作系统是否提供任何类型的锁定机制来防止它?

除非您指定一个,否则不会。

您的选择是:

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

实际上,只要您使用整数类型(不是bool(,使其volatile,并通过正确对齐其存储来保持自己的缓存行内,您根本不需要做任何特殊的事情。

在上述情况下,是否需要互斥锁?

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

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

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

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

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

我不希望在代码审查中看到这样的标志,但如果uint8_t标志是解决您正在解决的任何问题的最简单解决方案,我说去吧。if (globalVar)测试会将零视为false,而其他任何内容都视为true,因此暂时的"胡言乱语"是可以的,在实践中不会有任何奇怪的效果。根据标准,当然,您将面临未定义的行为。

它是否会发生通过不同线程同时访问(读/写(内存位置的情况,操作系统是否提供任何类型的锁定机制来防止它?

这不是操作系统的工作。

不过,说到实践:在任何合理的平台上,使用std::atomic_bool不会比使用裸uint8_t产生开销,所以只需使用它并完成即可。