总结自 Concurrency and lock-free Programming
规则 1 CP.43: Minimize time spent in a critical section
临界区越短越好!
C++ Core Guidelines: Be Aware of the Traps of Condition Variables 这篇文章中有这样一个代码段:
void setDataReady(){
std::lock_guard<std::mutex> lck(mutex_);
dataReady = true; // (1)
std::cout << 'Data prepared' << std::endl;
condVar.notify_one();
}mutex_ 在函数进入的时候上锁,在函数结束时解锁。这里只有修改 dataReady 的值需要上锁,因为 std::cout 是线程安全的。notify_one 也是线程安全的,是不需要加锁的。
所以,改进版代码的 setDataReady 函数如下:
void setDataReady(){
{ // Don't remove because of the lifetime of the mutex (1)
std::lock_guard<std::mutex> lck(mutex_);
dataReady = true;
} (2)
std::cout << 'Data prepared' << std::endl;
condVar.notify_one();
}(1)的注释最好不要删除,因为后面来接手或者维护这份代码的程序员(特别是比较初级的、对这个使用方法不熟悉的程序员)很可能觉得这个范围限定符是没有什么用的,所以会将其删掉。
规则2 CP.44: Remember to name your lock_guards and unique_locks
一定要给 lock_guard 和 unique_lock 取名,否则你想上锁的代码可能并没有正确得到锁。
unique_lock<mutex>(m1);
lock_guard<mutex> {m2};
lock(m1, m2);在上面的代码中, lock_guard 和 unique_lock 对象都是临时变量,创建之后立即被删除了。这种情况下,在构造函数中加锁,然后立即在析构函数中解锁(RAII:Garbage Collection: No Thanks)。
下面有一个简单的例子,能够展示 lock_guard 核心的东西:
// myGuard.cpp
#include <mutex>
#include <iostream>
template <typename T>
class MyGuard{
T& myMutex;
public:
MyGuard(T& m):myMutex(m){
myMutex.lock();
std::cout << 'lock' << std::endl;
}
~MyGuard(){
myMutex.unlock();
std::cout << 'unlock' << std::endl;
}
};
int main(){
std::cout << std::endl;
std::mutex m;
MyGuard<std::mutex> {m}; // (1)
std::cout << 'CRITICAL SECTION' << std::endl; // (2)
std::cout << std::endl;
} // (3)可以预想,(2)这条语句,其实会在没有加锁的情况下执行。运行结果如下:
$ ./a.out
lock
unlock
CRITICAL SECTION规则 3 Define a mutex together with the data it guards. Use synchronized_value where possible
这里的中心思想是:将 mutex 放入你想保护的数据中。标准写法如下:
struct Record {
std::mutex m; // take this mutex before accessing other members
// ...
};synchronized_value 暂时还不属于 C++ 标准,但是很快应该会被纳入。用法如下:
class MyClass {
struct DataRecord {
// ...
};
synchronized_value<DataRecord> data; // Protect the data with a mutex
};下面是一个示例:
synchronized_value<std::string> s;
std::string readValue()
{
return *s;
}
void setValue(std::string const& newVal)
{
*s=newVal;
}
void appendToValue(std::string const& extra)
{
s->append(extra);
}上面的所有操作都是线程安全的。
'The basic idea is that synchronized_value<T> stores a value of type T and a mutex. It then exposes a pointer interface, such that derefencing the pointer yields a special wrapper type that holds a lock on the mutex, and that can be implicitly converted to T for reading, and which forwards any values assigned to the assignment operator of the underlying T for writing.'
synchronized_value 的实现应该就是储存了一个 type 和一个 mutex。对外暴露一个指针接口,dereference 指针产生一个特殊的 wrapper 类型,首先获取 mutex 的锁,然后隐式转换为 T 用来读。另外,可以转发任何 “=” 操作符的操作数用来写。
————————————————————————
BY:HW-LH






































苏公安网备 32011402010933号