数据竞争问题分析
- 数据竞争:由于没有同步机制,两个线程同时对
sharedNum
进行修改操作,会导致数据竞争。例如,在 ptr++
和 ref++
操作过程中,可能会出现读取、修改、写回等步骤的交错执行,导致最终结果不符合预期。比如,假设 sharedNum
初始值为0,线程1读取 sharedNum
为0,线程2也读取 sharedNum
为0,然后线程1将其加1并写回,线程2也将其加1并写回,最终 sharedNum
的值为1而不是2。
使用原子操作避免数据竞争
- 原子类型:在C++ 中,可以使用
<atomic>
头文件中的原子类型来避免数据竞争。将 sharedNum
声明为 std::atomic<int>
类型。
#include <atomic>
std::atomic<int> sharedNum = 0;
- 原子操作:对于指针
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;
}
- 原子操作特性:
std::atomic
类型的操作是原子的,保证了在多线程环境下不会出现数据竞争,因为这些操作不会被其他线程打断。
使用互斥锁避免数据竞争
- 互斥锁定义:使用
<mutex>
头文件中的 std::mutex
来保护共享资源。
#include <mutex>
std::mutex mtx;
int sharedNum = 0;
- 加锁操作:在对
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();
}
- 互斥锁特性:
std::mutex
保证同一时间只有一个线程能够进入临界区(加锁和解锁之间的代码段),从而避免数据竞争。
引用和指针在同步工具使用时的考量
- 无本质区别:在使用原子操作或互斥锁时,引用和指针在同步机制的使用上没有本质区别。无论是通过指针还是引用访问共享变量,都需要对共享变量本身进行同步保护。无论是原子操作还是互斥锁,都是作用于共享变量
sharedNum
,而不是指针或引用本身。所以在同步方面,指针和引用的行为是一致的,都需要确保对共享变量的操作是线程安全的。