可能出现的问题
- 数据竞争:多个线程同时访问和修改
count
,由于CPU时间片的分配,可能导致数据不一致。例如一个线程读取count
的值,另一个线程在其修改前也读取了相同的值,然后两个线程分别进行修改后写回,最终结果并非预期的连续两次修改。
- 未定义行为:如果在多线程环境下对
count
的访问和修改没有适当的同步机制,可能会导致未定义行为,程序可能出现崩溃或产生不可预测的结果。
解决方案
1. 使用互斥锁(Mutex)
#include <iostream>
#include <mutex>
class C {
private:
int count;
std::mutex mtx;
public:
C() : count(0) {}
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++count;
}
int getCount() {
std::lock_guard<std::mutex> lock(mtx);
return count;
}
};
- 优点:
- 简单直观,易于理解和实现。
- 适用于多种场景,对数据访问的控制粒度可以根据需求灵活调整。
- 缺点:
- 性能开销,加锁和解锁操作会带来一定的时间开销,尤其是在高并发场景下,频繁的加解锁可能会成为性能瓶颈。
- 可能产生死锁,如果多个线程以不同顺序获取多个锁,可能会陷入死锁状态。
2. 使用原子操作(Atomic)
#include <iostream>
#include <atomic>
class C {
private:
std::atomic<int> count;
public:
C() : count(0) {}
void increment() {
++count;
}
int getCount() {
return count.load();
}
};
- 优点:
- 高效,原子操作由硬件直接支持,不需要像锁那样进行内核态的上下文切换,性能开销小。
- 避免死锁,因为不需要像锁那样进行复杂的锁获取和释放顺序管理。
- 缺点:
- 功能相对有限,只能对单个变量进行原子操作,对于复杂的数据结构或多个变量的组合操作难以直接支持。
- 可移植性问题,不同平台对原子操作的支持细节可能有所不同,在跨平台开发时需要注意兼容性。
3. 使用读写锁(Read - Write Lock)
#include <iostream>
#include <shared_mutex>
class C {
private:
int count;
std::shared_mutex rwMutex;
public:
C() : count(0) {}
void increment() {
std::unique_lock<std::shared_mutex> lock(rwMutex);
++count;
}
int getCount() {
std::shared_lock<std::shared_mutex> lock(rwMutex);
return count;
}
};
- 优点:
- 适用于读多写少的场景,读操作可以并发执行,提高了程序的并发性能。
- 相比于普通互斥锁,在读写操作比例不均衡的情况下,性能更好。
- 缺点:
- 实现相对复杂,需要区分读操作和写操作,并且要正确使用不同类型的锁。
- 写操作仍然是独占的,如果写操作频繁,可能会导致读操作长时间等待。