先明确谁负责释放
管理一项资源,首先要回答谁拥有它、谁可以借用它,以及什么时候释放它。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 不会自动决定合理的锁顺序,也不会避免所有死锁。上面的写法只负责锁的生命周期;锁粒度、并发不变量和线程之间的依赖,仍然属于设计工作。
文件写入是否成功、设备停止是否完成等可失败的业务操作,也应当有可检查的结果。不要把调用方必须知道的错误只藏在析构过程里。