MST

星途 面试题库

面试题:C++ 多线程全局变量的内存可见性及原子操作

在C++多线程编程中,全局变量的内存可见性是一个关键问题。请解释什么是内存可见性问题,当使用全局变量作为线程间通信的标志时(例如`bool flag = false;`,一个线程设置它为`true`,其他线程等待这个标志),可能会遇到什么问题?如何使用原子操作(`<atomic>`库)来解决这些问题?请给出代码示例并详细解释。
34.0万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

内存可见性问题

内存可见性是指在多线程编程中,一个线程对共享变量的修改,何时对其他线程可见。在现代计算机系统中,为了提高性能,CPU 可能会对指令进行重排序,并且每个 CPU 核心都有自己的缓存。这就导致一个线程修改了共享变量的值,这个修改可能暂时只存在于该线程所在 CPU 核心的缓存中,还未被写回主内存,其他线程从主内存读取该变量时,就无法立即看到这个修改,从而引发内存可见性问题。

使用全局变量作为线程间通信标志可能遇到的问题

  1. 可见性问题:如上述内存可见性问题描述,一个线程修改了 bool flag,其他线程可能无法及时看到这个修改,导致等待标志的线程一直处于等待状态。
  2. 竞态条件:多个线程同时访问和修改 flag 时,可能会出现数据竞争。例如,一个线程在读取 flag 和判断 flag 的值之间,另一个线程修改了 flag,可能导致逻辑错误。

使用原子操作(<atomic>库)解决问题

  1. <atomic>库简介<atomic>库提供了一系列原子类型和操作,这些操作保证了在多线程环境下的原子性和内存可见性。原子操作是不可分割的操作,要么完全执行,要么完全不执行,不存在中间状态。
  2. 代码示例
#include <iostream>
#include <thread>
#include <atomic>

std::atomic<bool> flag(false);

void setFlag() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    flag.store(true); // 设置标志为 true
    std::cout << "Flag set to true" << std::endl;
}

void waitForFlag() {
    while (!flag.load()) { // 等待标志变为 true
        std::this_thread::yield(); // 让出 CPU 时间片
    }
    std::cout << "Flag is true, continuing..." << std::endl;
}

int main() {
    std::thread t1(setFlag);
    std::thread t2(waitForFlag);

    t1.join();
    t2.join();

    return 0;
}
  1. 代码解释
    • std::atomic<bool> flag(false);:定义一个原子类型的 bool 变量 flag,初始值为 false
    • flag.store(true);:在 setFlag 函数中,使用 store 方法将 flag 设置为 truestore 操作保证了修改的原子性和内存可见性,即修改会立即对其他线程可见。
    • while (!flag.load()):在 waitForFlag 函数中,使用 load 方法读取 flag 的值。load 操作也保证了内存可见性,确保能读取到最新的值。
    • std::this_thread::yield();:在等待 flag 变为 true 的循环中,调用 yield 方法让出 CPU 时间片,避免不必要的 CPU 消耗。

通过使用 <atomic> 库中的原子类型和操作,我们可以有效地解决多线程环境下全局变量作为通信标志时的内存可见性和竞态条件问题。