数据竞争问题对效率的影响
- 性能降低:当多个线程同时访问和修改共享数据时,由于数据竞争,会导致程序的行为变得不可预测。为了修复数据竞争问题,通常需要引入同步机制(如互斥锁),这会带来额外的开销,如加锁和解锁操作,从而降低程序的执行效率。
- 缓存一致性问题:现代处理器都有缓存机制,不同线程可能在各自的缓存中持有共享数据的副本。数据竞争可能导致缓存一致性协议频繁地在处理器间同步数据,增加了内存访问的延迟,进一步降低效率。
保障线程安全并减少效率负面影响的手段
- 互斥锁:
- 原理:互斥锁(
std::mutex
)用于保护共享资源,同一时间只有一个线程可以获取锁并访问资源,其他线程必须等待锁被释放。
- 优点:简单直观,能有效防止数据竞争。
- 缺点:加锁和解锁操作有一定开销,如果频繁加锁解锁,会严重影响性能。
- 示例:
#include <iostream>
#include <mutex>
#include <thread>
class Counter {
public:
Counter() : value(0) {}
void increment() {
std::lock_guard<std::mutex> lock(mutex_);
++value;
}
int getValue() const {
std::lock_guard<std::mutex> lock(mutex_);
return value;
}
private:
int value;
mutable std::mutex mutex_;
};
int main() {
Counter counter;
std::thread t1([&counter]() {
for (int i = 0; i < 1000000; ++i) {
counter.increment();
}
});
std::thread t2([&counter]() {
for (int i = 0; i < 1000000; ++i) {
counter.increment();
}
});
t1.join();
t2.join();
std::cout << "Final value: " << counter.getValue() << std::endl;
return 0;
}
- 原子操作:
- 原理:原子操作(如
std::atomic
)是不可分割的操作,在执行过程中不会被其他线程打断,不需要额外的锁机制。
- 优点:比互斥锁效率高,适合简单数据类型的操作,如整数的加减等。
- 缺点:功能相对有限,对于复杂数据结构难以直接使用。
- 示例:
#include <iostream>
#include <atomic>
#include <thread>
class AtomicCounter {
public:
AtomicCounter() : value(0) {}
void increment() {
++value;
}
int getValue() const {
return value.load();
}
private:
std::atomic<int> value;
};
int main() {
AtomicCounter counter;
std::thread t1([&counter]() {
for (int i = 0; i < 1000000; ++i) {
counter.increment();
}
});
std::thread t2([&counter]() {
for (int i = 0; i < 1000000; ++i) {
counter.increment();
}
});
t1.join();
t2.join();
std::cout << "Final value: " << counter.getValue() << std::endl;
return 0;
}
不同情况下的最优选择
- 简单数据类型的简单操作:优先使用原子操作,如
std::atomic<int>
进行整数的加减等操作,因为其无需额外锁开销,效率高。
- 复杂数据结构或复杂操作:使用互斥锁来保护共享资源。虽然有锁开销,但能确保复杂操作的原子性和线程安全。
- 读多写少场景:可以考虑使用读写锁(
std::shared_mutex
),允许多个线程同时读,但写操作需要独占锁,这样在读操作频繁时能提高效率。
#include <iostream>
#include <shared_mutex>
#include <thread>
#include <vector>
class SharedData {
public:
void write(int data) {
std::unique_lock<std::shared_mutex> lock(mutex_);
data_.push_back(data);
}
void read() const {
std::shared_lock<std::shared_mutex> lock(mutex_);
for (int num : data_) {
std::cout << num << " ";
}
std::cout << std::endl;
}
private:
std::vector<int> data_;
mutable std::shared_mutex mutex_;
};
int main() {
SharedData sharedData;
std::thread writer([&sharedData]() {
for (int i = 0; i < 10; ++i) {
sharedData.write(i);
}
});
std::thread reader1([&sharedData]() {
sharedData.read();
});
std::thread reader2([&sharedData]() {
sharedData.read();
});
writer.join();
reader1.join();
reader2.join();
return 0;
}