multiple threads synchronization primitive: 多线程同步语义

多线程的同步语义是多线程编程的核心,线程之间通过同步语义进行通信,实现并发。C++ JAVA 中线程同步的基本原语是condition variable 和mutex构成的管程 ,OS操作系统课程中经常出现的信号量(Semaphore)语义在实际编程中比较少见。我目前工作中只用过mutex+condvar,或者在它们之上的高层抽象,C++11 中的future和promise.

那么C++11 中的标准库已经支持std::condition_variable and mutex 。 所谓线程同步,就是线程之间的通信 ,传统的线程之间通信利用的是shared memory 共享内存的方式。 比如说productor 和consumer model,生产者thread和消费者thread 如何相互通信,就是利用shared memory 的buffer,buffer是threads之间沟通的桥梁。 生产者消费者都可以write 和read buffer的data. 这就引入了race condition 竞争态,会造成各个thread 视角下的data invariance .单线程内我们read data的invariance被破坏。这跟指令重排或者编译器重排的问题不一样(内存么模型更强调的是happen before语义,两个线程视角下的数据不一致,而不是单个线程下的不一致),race condition 包括语句之间的race condition 和单个语句例如i++非原子性导致的race condition.根本原因都是threads之间穿插执行.

解决方法就是mutex,变并发为串行。同时mutex也可以用于两个线程视角下同个变量值线程不一致的问题. mutex有两层语义:

1. 保证了一个线程lock(mutex)和unlock(mutex)之间保护的语句 肯定在另外一个线程lock(mutex)之前可以visible。

2.原子操作,unlock 之前。别的线程不能执行。解决race condition

回到线程同步中,mutex保护的对象就是buffer这个共享内存, 我们用predictor谓词 表示判断的内容。

pthread_mutex_lock(&mutex);
while (condition == FALSE)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex); pthread_mutex_lock(&mutex);
condition = TRUE;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

1. 为什么cond wait需要关联一个mutex互斥锁.因为我们需要mutex保护共享内存.一个线程调用wait之后,我们应该先将线程加入等待队列中,然后unlock mutex. 因为先加入等待队列,然后unlock的顺序,所以我们无法不传入mutex。

这要求我们生成的线程一定要先lock mutex,然后才能操作buffer。否则不存在约束。保证两个线程之间的同步。

以上的add the waiting queue 和unlock mutex 的原子性依赖一个前提条件:唤醒者在调用pthread_cond_broadcast或pthread_cond_signal唤醒等待者之前也必须对相同的mutex加锁。

如果没有这个条件,那么为了保证原子性我们需要在wait 和signal内部实现中引入mutexB 去实现真正的原子性依赖.

c++11 实现:

std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});

2.虚假唤醒用while 解决:  

while(predictor)为了防止虚假唤醒。两方面原因:

  • 第一个原因就是wait的系统调用system call 被信号中断了。这时候如果需要重试,那么在判断和重试之间有race condition,此时都是无锁状态的. 即便想加锁也来不及了。判断是否需要加锁和加锁的race condition。
  • 另外的原因就是wait被唤醒之后,要lock互斥锁。假如在此之前有其他线程抢占了mutex锁,然后更新了predictor为false。这时候再次切换回来就会虚假唤醒
  • 不能用if代替,一个生产者可能对应着多个消费者,生产者向队列中插入一条数据之后发出signal,然后各个消费者线程的pthread_cond_wait获取mutex后返回,当然,这里只有一个线程获取到了mutex,然后进行处理,其它线程会pending在这里,处理线程处理完毕之后释放mutex,刚才等待的线程中有一个获取mutex,如果这里用if,就会在当前队列为空的状态下继续往下处理,这显然是不合理的.

3. signal到底是放在unlock之前还是之后??

void
enqueue_msg(struct msg *mp)
{
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&qready);
}

如果先unlock,再signal,如果这时候有一个消费者线程恰好获取mutex,然后进入条件判断,这里就会判断成功,从而跳过pthread_cond_wait,下面的signal就会不起作用;另外一种情况,一个优先级更低的不需要条件判断的线程正好也需要这个mutex,这时候就会转去执行这个优先级低的线程,就违背了设计的初衷。

    void
enqueue_msg(struct msg *mp)
{
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_cond_signal(&qready);
pthread_mutex_unlock(&qlock);
}

如果把signal放在unlock之前,消费者线程会被唤醒,获取mutex发现获取不到,就又去sleep了。浪费了资源.但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
所以在Linux中推荐使用这种模式。

以上参考:

https://www.cnblogs.com/harlanc/p/8596211.html

c+11 std::condition_variable and mutex的更多相关文章

  1. C++11 并发指南五(std::condition_variable 详解)

    前面三讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解)>分别介绍了 std::thread,std::mut ...

  2. C++11并发——多线程条件变量std::condition_variable(四)

    https://www.jianshu.com/p/a31d4fb5594f https://blog.csdn.net/y396397735/article/details/81272752 htt ...

  3. 基于std::mutex std::lock_guard std::condition_variable 和std::async实现的简单同步队列

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为.通常的做法是在修改共享数据成员的时候进行加锁--mutex.在使用锁的时候通 ...

  4. 转 C++11 并发指南std::condition_variable详解

    之前看过,但是一直没有怎么用就忘了,转一篇别人的文字记录下来 本文将介绍 C++11 标准中 <condition_variable> 头文件里面的类和相关函数. <conditio ...

  5. C++11 并发指南五(std::condition_variable 详解)(转)

    前面三讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解)>分别介绍了 std::thread,std::mut ...

  6. 【转】C++11 并发指南五(std::condition_variable 详解)

    http://www.cnblogs.com/haippy/p/3252041.html 前面三讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三 ...

  7. c++11 线程间同步---利用std::condition_variable实现

    1.前言 很多时候,我们在写程序的时候,多多少少会遇到下面种需求 一个产品的大致部分流程,由工厂生产,然后放入仓库,最后由销售员提单卖出去这样. 在实际中,仓库的容量的有限的,也就是说,工厂不能一直生 ...

  8. C++11 thread condition_variable mutex 综合使用

    #include <mutex> #include <condition_variable> #include <chrono> #include <thre ...

  9. 通过c++11的condition_variable实现的有最大缓存限制的队列

    之前曾写过一个通过C++11的condition_variable实现的有最大缓存限制的队列,底层使用std::queue来实现,如果想要提升性能的话,可以考虑改用固定的长度环形数组.环形数组实现如下 ...

随机推荐

  1. vs下载代码

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u012605477/article/details/62222919Visual Studio 20 ...

  2. 持续集成学习9 jenkins执行脚本

    一.配置 1.首先在slave节点上写一脚本 [root@node1 script]# cat /application/script/test.sh #!/bin/bash echo "h ...

  3. C博客作业

    1.你对网络专业或者计算机专业了解是怎样? 信息化是国企的一个大趋势,目前正是红火的时候. - 网络是信息化必不可少的的基础和平台,随着信息化的进步,网络也必将水涨船高. - 我认为网络方向主要学的是 ...

  4. Hyperspectral Image Classification Using Similarity Measurements-Based Deep Recurrent Neural Networks

    用RNN来做像素分类,输入是一系列相近的像素,长度人为指定为l,相近是利用像素相似度或是范围相似度得到的,计算个欧氏距离或是SAM. 数据是两个高光谱数据 1.Pavia University,Ref ...

  5. 深度讨论i++问题

    例题1:下列程序的输出结果是多少? public class Test { static { int x = 5; } static int x, y; public static void main ...

  6. 【整理】Xcode中的iOS模拟器(iOS Simulator)的介绍和使用心得

    [整理]Xcode中的iOS模拟器(iOS Simulator)的介绍和使用心得 iOS模拟器简介 iOS功能简介 iOS模拟器,是在Mac下面开发程序时,开发iOS平台的程序时候,可以使用的辅助工具 ...

  7. pycharm无法识别自己的文件夹的程序

    网上找教程折腾了半天也没解决,然后我换了一下文件夹名称…… 文件夹名称不能用数字开头,否则识别不出来.

  8. 【数值分析】Python实现Lagrange插值

    一直想把这几个插值公式用代码实现一下,今天闲着没事,尝试尝试. 先从最简单的拉格朗日插值开始!关于拉格朗日插值公式的基础知识就不赘述,百度上一搜一大堆. 基本思路是首先从文件读入给出的样本点,根据输入 ...

  9. 升级pip后,出现ImportError:cannot import name main

    升级pip后,出现ImportError错误,如下图: 解决方法: sudo gedit /usr/bin/pip 进去后修改为 from pip import __main__ if __name_ ...

  10. Can not add Newtonsoft.Json.Linq.JValue to Newtonsoft.Json.Linq.JObject.

    https://blog.csdn.net/zhouyingge1104/article/details/83307637 C#项目中使用NewtonSoft.json,报错提示: Can not a ...