MST

星途 面试题库

面试题:C++ 引用和指针自增运算在多线程环境下的考虑

在一个多线程C++ 程序中,有一个共享的整型变量 `int sharedNum = 0;`,一个线程中定义指向该变量的指针 `int* ptr = &sharedNum;` 并在一个循环中进行 `ptr++;` 操作,另一个线程中定义对该变量的引用 `int& ref = sharedNum;` 并在另一个循环中进行 `ref++;` 操作。假设这两个线程同时运行,且没有任何同步机制。请分析这种情况下可能出现的数据竞争问题,如何通过C++ 的原子操作或其他同步工具(如互斥锁)来避免这些问题,同时说明引用和指针在使用这些同步工具时是否有不同的考量。
37.0万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

数据竞争问题分析

  1. 数据竞争:由于没有同步机制,两个线程同时对 sharedNum 进行修改操作,会导致数据竞争。例如,在 ptr++ref++ 操作过程中,可能会出现读取、修改、写回等步骤的交错执行,导致最终结果不符合预期。比如,假设 sharedNum 初始值为0,线程1读取 sharedNum 为0,线程2也读取 sharedNum 为0,然后线程1将其加1并写回,线程2也将其加1并写回,最终 sharedNum 的值为1而不是2。

使用原子操作避免数据竞争

  1. 原子类型:在C++ 中,可以使用 <atomic> 头文件中的原子类型来避免数据竞争。将 sharedNum 声明为 std::atomic<int> 类型。
#include <atomic>
std::atomic<int> sharedNum = 0;
  1. 原子操作:对于指针 ptr 和引用 ref,操作时直接对 sharedNum 进行原子操作。例如:
// 线程1
std::atomic<int>* ptr = &sharedNum;
for (int i = 0; i < 1000; ++i) {
    ++(*ptr);
}

// 线程2
std::atomic<int>& ref = sharedNum;
for (int i = 0; i < 1000; ++i) {
    ++ref;
}
  1. 原子操作特性std::atomic 类型的操作是原子的,保证了在多线程环境下不会出现数据竞争,因为这些操作不会被其他线程打断。

使用互斥锁避免数据竞争

  1. 互斥锁定义:使用 <mutex> 头文件中的 std::mutex 来保护共享资源。
#include <mutex>
std::mutex mtx;
int sharedNum = 0;
  1. 加锁操作:在对 sharedNum 进行操作前加锁,操作完成后解锁。
// 线程1
int* ptr = &sharedNum;
for (int i = 0; i < 1000; ++i) {
    mtx.lock();
    ++(*ptr);
    mtx.unlock();
}

// 线程2
int& ref = sharedNum;
for (int i = 0; i < 1000; ++i) {
    mtx.lock();
    ++ref;
    mtx.unlock();
}
  1. 互斥锁特性std::mutex 保证同一时间只有一个线程能够进入临界区(加锁和解锁之间的代码段),从而避免数据竞争。

引用和指针在同步工具使用时的考量

  1. 无本质区别:在使用原子操作或互斥锁时,引用和指针在同步机制的使用上没有本质区别。无论是通过指针还是引用访问共享变量,都需要对共享变量本身进行同步保护。无论是原子操作还是互斥锁,都是作用于共享变量 sharedNum,而不是指针或引用本身。所以在同步方面,指针和引用的行为是一致的,都需要确保对共享变量的操作是线程安全的。