先明确谁负责释放

管理一项资源,首先要回答谁拥有它、谁可以借用它,以及什么时候释放它。C++ Core Guidelines 的 R.1 建议通过资源句柄和 RAII 自动管理资源。

RAII 把资源管理与对象生命周期关联起来。对于一个正确构造的局部资源管理对象,正常离开作用域或异常展开时,析构函数会执行相应清理。

把锁交给局部对象

下面是一个可独立编译的最小例子。互斥锁由计数器持有,单次访问的加锁与解锁由局部的 std::lock_guard 负责。

#include <mutex>

class Counter {
public:
    void Increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++value_;
    }

    int Value() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return value_;
    }

private:
    mutable std::mutex mutex_;
    int value_ = 0;
};

阅读函数时,锁的保护范围就在当前代码块中。需要缩小临界区时,可以使用更小的作用域,而不必让手动解锁分散在多个返回路径里。

仍然需要设计的部分

RAII 不会自动决定合理的锁顺序,也不会避免所有死锁。上面的写法只负责锁的生命周期;锁粒度、并发不变量和线程之间的依赖,仍然属于设计工作。

文件写入是否成功、设备停止是否完成等可失败的业务操作,也应当有可检查的结果。不要把调用方必须知道的错误只藏在析构过程里。

延伸阅读

C++ Core Guidelines · R.1 ↗