原文:https://www.modernescpp.com/index.php/c-core-guidelines-concurrency-and-lock-free-programming
今天,我完成了并发性规则,并直接继续进行无锁编程。是的,您已正确阅读:无锁编程。
在我特别写无锁编程之前,这是并发的最后三个规则。
CP.44:请记住命名您的lock_guards和unique_locks
CP.50:定义一个mutex及其所保护的数据。synchronized_value<T>尽可能使用
我之所以简短,是因为这些规则非常明显。
CP.43:尽量减少在关键部分花费的时间
锁定互斥锁的时间越短,其他线程可以运行的时间就越多。查看条件变量的通知。如果您想查看整个程序,请阅读我以前的文章C ++核心准则:警惕条件变量的陷阱。
void setDataReady(){
std::lock_guard<std::mutex> lck(mutex_);
dataReady = true; // (1)
std::cout << "Data prepared" << std::endl;
condVar.notify_one();
}
Mutex Mutex_在函数开始时被锁定,在函数结束时被解锁。这不是必需的。仅表达式dataReady = true(1)必须受到保护。
首先,std :: cout是线程安全的。C ++ 11标准保证每个字符都是按原子步骤并按正确顺序编写的。其次,通知condVar.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();
}
CP.44:请记住命名您的lock_guards和unique_locks
我有点惊讶地读了这条规则。以下是准则中的示例:
unique_lock<mutex>(m1);
lock_guard<mutex> {m2};
lock(m1, m2);
该unique_lock和lock_guard是被创建并立即销毁只是临时工。该的std :: lock_guard或std:: unique_lock锁定其互斥它的构造和取消锁定在其析构函数。这种模式称为RAII。在此处阅读详细信息:垃圾收集:不用了,谢谢。
我的小示例仅显示概念行为std :: lock_guard。它的大哥std :: unique_lock支持更多操作。
// 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)
MyGuard在其构造函数和其析构函数中调用锁定和解锁。由于是临时的,因此对构造函数和析构函数的调用发生在第(1)行中。特别是,这意味着析构函数的调用发生在第(1)行,而不像通常那样发生在第(3)行。因此,第(2)行中的关键部分将不同步执行。
该程序的执行表明,“ unlock ”的输出在“ CRITICAL SECTION ” 的输出之前发生。
CP.50:定义一个mutex及其所保护的数据。synchronized_value<T>尽可能使用
中心思想是将互斥锁放入要保护的数据中。使用已经标准化的C ++,它看起来像这样:
struct Record {
std::mutex m; // take this mutex before accessing other members
// ...
};
对于即将发布的标准,它可能看起来像这样,因为synchronized_value<T>不是当前C ++标准的一部分,而是可能成为即将发布的标准的一部分。
class MyClass {
struct DataRecord {
// ...
};
synchronized_value<DataRecord> data; // Protect the data with a mutex
};
根据Anthony Williams 的建议N4033:“ 基本思想是synchronized_value<T>存储类型T和互斥量的值。然后它公开了一个指针接口,这样对指针的取消引用会产生一个特殊的包装类型,该类型将互斥量锁定,并可以隐式转换为T用于读取,并转发分配给基础的赋值运算符的任何值以T进行写入。”
这意味着以下代码段中对的操作是线程安全的。
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);
}
现在开始一些完全不同方式:无锁编程。
无锁编程
首先,让我说说无锁编程最重要的元规则。
不要无锁编程
当然,您不相信我,但是基于我参加许多并发课程和研讨会的经验,这是我的第一条规则。老实说,我同意世界上许多最受赞赏的C ++专家的意见。以下是他们的演讲的主要声明和引用:
Herb Sutter:无锁编程就像玩刀一样。
Anthony Williams:“无锁编程是关于如何用脚射击自己。 ”
Tony Van Eerd:“无锁编码是您要做的最后一件事。”
Fedor Pikus:“ 编写正确的无锁程序更加困难。 ”
HaraldBöhm:“ 规则并不明显。 ”
这是语句和引用的图片:
你还是不相信我 使用C ++ 11,定义了内存顺序std :: memory_order_consume。七年后,官方用语是:“ 发布-使用订购的规范正在修订,memory_order_consume暂时不鼓励使用。 ”(memory_order)
如果您知道自己做了什么,请考虑 准则CP.100中的ABA问题。
C ++核心指南中的以下代码片段存在bug。
extern atomic<Link*> head; // the shared head of a linked list
Link* nh = new Link(data, nullptr); // make a link ready for insertion
Link* h = head.load(); // read the shared head of the list
do {
if (h->data <= data) break; // if so, insert elsewhere
nh->next = h; // next element is the previous head
} while (!head.compare_exchange_weak(h, nh)); // write nh to head or to h
找到错误并给我写一封电子邮件。如果您喜欢自己的名字,我将在下一篇文章中提及最佳问题分析。