Bootstrap

每日面试题-C++ 中如何设计一个线程安全的类?

在C++中设计线程安全的类需要遵循以下核心原则和技术实现:


1. 基本原则

  • 临界资源全保护:对所有可能被并发访问的成员变量进行同步控制
  • 接口原子化:确保每个公共成员函数的操作具有原子性
  • 避免隐性共享:防止通过返回值/参数暴露内部数据引用
  • 异常安全:保证锁机制在异常发生时仍能正确释放

2. 标准实现模式

#include <mutex>
#include <vector>

class ThreadSafeVector {
public:
    void push(int value) {
        std::lock_guard<std::mutex> lock(mtx_);
        data_.push_back(value);
    }

    int at(size_t index) const {
        std::lock_guard<std::mutex> lock(mtx_);
        if(index >= data_.size()) throw std::out_of_range("");
        return data_[index];
    }

    size_t size() const {
        std::lock_guard<std::mutex> lock(mtx_);
        return data_.size();
    }

private:
    mutable std::mutex mtx_;  // mutable允许const函数加锁
    std::vector<int> data_;
};

3. 高级优化技术

3.1 细粒度锁
class BankAccount {
public:
    void transfer(double amount, BankAccount& to) {
        std::unique_lock<std::mutex> lock1(mtx_, std::defer_lock);
        std::unique_lock<std::mutex> lock2(to.mtx_, std::defer_lock);
        std::lock(lock1, lock2);  // 避免死锁
        
        balance_ -= amount;
        to.balance_ += amount;
    }

private:
    std::mutex mtx_;
    double balance_ = 0;
};
3.2 读写锁(C++17+)
#include <shared_mutex>

class ConfigManager {
public:
    std::string getConfig(const std::string& key) const {
        std::shared_lock lock(rw_mtx_);  // 共享读锁
        return configs_.at(key);
    }

    void updateConfig(const std::string& key, const std::string& value) {
        std::unique_lock lock(rw_mtx_);  // 独占写锁
        configs_[key] = value;
    }

private:
    mutable std::shared_mutex rw_mtx_;
    std::unordered_map<std::string, std::string> configs_;
};

4. 关键注意事项

返回值陷阱

// 危险:返回内部指针
const int* unsafeGetPtr() const {
    std::lock_guard lock(mtx_);
    return &data_;  // 锁释放后指针可能失效
}

// 安全:返回副本
std::vector<int> safeGetCopy() const {
    std::lock_guard lock(mtx_);
    return data_;
}

构造/析构安全

  • 构造函数不需要同步(对象尚未完全创建)
  • 析构函数需确保无其他线程正在访问对象

移动语义处理

class SafeObject {
public:
    SafeObject(SafeObject&& other) noexcept {
        std::lock_guard lock1(mtx_);
        std::lock_guard lock2(other.mtx_);
        data_ = std::move(other.data_);
    }
};

5. 验证方法

  • 静态分析:使用Clang ThreadSanitizer
  • 压力测试
void concurrent_test() {
    ThreadSafeVector vec;
    std::vector<std::thread> threads;
    
    for(int i=0; i<10; ++i){
        threads.emplace_back([&vec, i](){
            for(int j=0; j<1000; ++j){
                vec.push(i*1000 + j);
            }
        });
    }
    
    for(auto& t : threads) t.join();
    assert(vec.size() == 10000);
}

6. 设计模式选择

模式适用场景性能影响
全局互斥锁简单数据结构/低频操作中等
读写锁读多写少场景较低
无锁编程高性能关键路径最低
分段锁大规模哈希表等结构
;