什么是线程安全?
线程安全 指的是在多线程环境中,当多个线程同时访问同一资源 (变量、数据结构、对象等)时,程序能够保证数据的一致性 和正确性 ,不会出现数据损坏 或逻辑错误。
一、非线程安全的例子:
cpp
#include <iostream>
#include <thread>
int counter = 0; // 共享资源
void increment() {
for (int i = 0; i < 100000; ++i) {
counter++; // 非原子操作,可能产生数据竞争
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
// 结果可能不是200000
std::cout << "Counter: " << counter << std::endl;
return 0;
}
二、实现线程安全的常见方法(C++示例)
1. 互斥锁 (Mutex)
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁解锁
counter++;
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl; // 总是200000
return 0;
}
2. 原子操作 (Atomic)
cpp
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> counter(0); // 原子变量
void increment() {
for (int i = 0; i < 100000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
// 或 counter++ (C++11保证atomic的自增是原子的)
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl;
return 0;
}
3. 读写锁 (Read-Write Lock)
cpp
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
std::shared_mutex rw_mutex;
std::vector<int> data = {1, 2, 3};
void reader(int id) {
std::shared_lock<std::shared_mutex> lock(rw_mutex); // 共享锁
std::cout << "Reader " << id << " sees: ";
for (int val : data) std::cout << val << " ";
std::cout << std::endl;
}
void writer(int id) {
std::unique_lock<std::shared_mutex> lock(rw_mutex); // 独占锁
data.push_back(data.back() + 1);
std::cout << "Writer " << id << " added: " << data.back() << std::endl;
}
4. 条件变量 (Condition Variable)
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool finished = false;
void producer() {
for (int i = 0; i < 10; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
std::cout << "Produced: " << i << std::endl;
cv.notify_one(); // 通知消费者
}
std::lock_guard<std::mutex> lock(mtx);
finished = true;
cv.notify_all();
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !data_queue.empty() || finished; });
while (!data_queue.empty()) {
int data = data_queue.front();
data_queue.pop();
std::cout << "Consumed: " << data << std::endl;
}
if (finished) break;
}
}
5. 线程局部存储 (Thread Local Storage)
cpp
#include <iostream>
#include <thread>
thread_local int thread_specific_value = 0; // 每个线程独立副本
void worker(int id) {
thread_specific_value = id;
for (int i = 0; i < 3; ++i) {
thread_specific_value++;
std::cout << "Thread " << id << ": " << thread_specific_value << std::endl;
}
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join();
t2.join();
return 0;
}
6. 不可变对象 (Immutable Objects)
cpp
#include <iostream>
#include <string>
#include <thread>
// 不可变类 - 所有成员都是const,创建后不可修改
class ImmutableData {
private:
const int id_;
const std::string name_;
public:
ImmutableData(int id, std::string name) : id_(id), name_(std::move(name)) {}
int getId() const { return id_; }
const std::string& getName() const { return name_; }
};
void useData(const ImmutableData& data) {
// 可以安全地在多个线程中访问,因为对象不可变
std::cout << "ID: " << data.getId() << ", Name: " << data.getName() << std::endl;
}
三、线程安全设计原则
1. 最小化共享数据 :尽量设计无状态或线程局部的对象
2. 使用标准库设施 :优先使用std::atomic、智能指针等线程安全组件
3. 避免数据竞争 :确保对共享数据的访问有适当的同步
4. 避免死锁:
cpp
// 坏例子:可能导致死锁
std::mutex mtx1, mtx2;
void thread1() {
std::lock_guard<std::mutex> lock1(mtx1);
std::lock_guard<std::mutex> lock2(mtx2); // 如果thread2先锁mtx2,会死锁
}
// 好例子:使用std::lock同时锁定多个互斥量
void thread1_safe() {
std::lock(mtx1, mtx2); // 一次性锁定,避免死锁
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
}
5.优先使用RAII:使用lock_guard、unique_lock等自动管理锁的生命周期
四、性能考虑
· 互斥锁有开销,过度使用会降低性能
· 读写锁在读多写少的场景中性能更好
· 原子操作通常比互斥锁更高效
· 无锁编程复杂但性能可能更好,适合高级场景
选择哪种线程安全机制取决于具体场景:数据竞争频率、读写比例、性能要求等。