C ++核心准则:并发和无锁编程

原文:https://www.modernescpp.com/index.php/c-core-guidelines-concurrency-and-lock-free-programming

今天,我完成了并发性规则,并直接继续进行无锁编程。是的,您已正确阅读:无锁编程。

在我特别写无锁编程之前,这是并发的最后三个规则。

CP.43:尽量减少在关键部分花费的时间

CP.44:请记住命名您的lock_guards和unique_locks

CP.50:定义一个mutex及其所保护的数据。synchronized_value尽可能使用

我之所以简短,是因为这些规则非常明显。

CP.43:尽量减少在关键部分花费的时间

锁定互斥锁的时间越短,其他线程可以运行的时间就越多。查看条件变量的通知。如果您想查看整个程序,请阅读我以前的文章C ++核心准则:警惕条件变量的陷阱。

void setDataReady(){

    std::lock_guard 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 lck(mutex_);

        dataReady = true;

    }                                                      (2)

    std::cout << "Data prepared" << std::endl;

    condVar.notify_one();             

}

CP.44:请记住命名您的lock_guards和unique_locks

我有点惊讶地读了这条规则。以下是准则中的示例:

unique_lock(m1);

lock_guard {m2};

lock(m1, m2);

该unique_lock和lock_guard是被创建并立即销毁只是临时工。该的std :: lock_guard或std:: unique_lock锁定其互斥它的构造和取消锁定在其析构函数。这种模式称为RAII。在此处阅读详细信息:垃圾收集:不用了,谢谢。

我的小示例仅显示概念行为std :: lock_guard。它的大哥std :: unique_lock支持更多操作。

// myGuard.cpp

#include

#include

template

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 {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尽可能使用

中心思想是将互斥锁放入要保护的数据中。使用已经标准化的C ++,它看起来像这样:

struct Record {

    std::mutex m;  // take this mutex before accessing other members

    // ...

};

对于即将发布的标准,它可能看起来像这样,因为synchronized_value不是当前C ++标准的一部分,而是可能成为即将发布的标准的一部分。

class MyClass {

    struct DataRecord {

      // ...

    };

    synchronized_value data; // Protect the data with a mutex

};


根据Anthony Williams 的建议N4033:“ 基本思想是synchronized_value存储类型T和互斥量的值。然后它公开了一个指针接口,这样对指针的取消引用会产生一个特殊的包装类型,该类型将互斥量锁定,并可以隐式转换为T用于读取,并转发分配给基础的赋值运算符的任何值以T进行写入。”

这意味着以下代码段中对的操作是线程安全的。

synchronized_value 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问题。

CP.100:除非绝对必要,否则不要使用无锁编程

C ++核心指南中的以下代码片段存在bug。

extern atomic 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


找到错误并给我写一封电子邮件。如果您喜欢自己的名字,我将在下一篇文章中提及最佳问题分析。

你可能感兴趣的:(C ++核心准则:并发和无锁编程)