多线程---条件变量

互斥器和条件变量的区别:互斥器具有加锁原语,用来进行排他性的访问共享数据,而条件变量具有等待原语,用于等待某个事件的发生。

等待条件变量的正确姿势:

复制代码
void wait() {mutex.lock()while (wait_flag == false) {condition.wait()}do_something();mutex.unlock();
}
复制代码

1)必须使用while循环来等待条件变为真,即醒来之后要立马再判断一次条件是否成立再决定是否需要继续等待,

因为很有可能条件并不为真,但是线程却被各种奇怪的中断或者pthread_cond_broadcast这样的东西给唤醒了

 

2)至于condition.wait()的作用是提供一个原子操作:

进入condition.wait() 时,将 wait 和 mutex.unlock 两步变成一个原子操作,确保在线程进入 wait 之前不会释放锁,也就是保证了在进入 wait 之前没有人能够改动 wait_flag (所有对 wait_flag的操作都要加锁,否则条件变量没有意义),

因为pthread_cond_signal这类函数只会唤醒已经在等待队列里的线程,如果这两步不是原子操作,那么在释放锁之后,进入等待队列之前的这段时间里,有人调用了cond_signal之后也不会被唤醒

 

触发条件的正确姿势:

复制代码
void notify() {mutex.lock();push_resource();condition.signal();mutex.unlock();
}void notifyAll() {mutex.lock();change_status();condition.broadcast();mutex.unlock();
}
复制代码

 

通常在资源可用时使用signal,在改变状态时使用broadcast。

这里特别说明一下网上流传的阻塞队列 (为简单起见,这里假设队列最大长度为无限长) 的实现有个错误:

复制代码
void push(e) {mutex.lock();queue.push(e);if (queue.size() == 1) { //错误,应该把if条件去掉cond.notify();        //或者不去掉if,改成notifyAll
    }mutex.unlock();
}
复制代码

因为pthread_cond_signal只是唤醒线程到就绪队列,至于这个线程能不能抢到锁以及抢到锁之后能不能保证立马被调度并从队列里取元素就不保证了。

所以会出现这种情况,有多个饥饿的线程在等待队列变成非空,这个时候有一个线程放进去一个元素,之后唤醒了一个等待线程到就绪队列,但是他却没有获取到锁,

这个时候其他的线程往队列里push的时候,由于消费者线程没有来得及取元素,所以队列元素的总量大于1,所以并不触发signal。。。

这就导致在下次队列变为空之前,可能只有一个消费者线程会被唤醒。

使用notifyAll可以避免这个问题,但是代价是虚假唤醒(据说对这种情况会优化,这里不讨论。。恩,因为我特么也是不懂啊,记住正确的做法不就好了么)

所以对于有多个消费者线程的阻塞队列,正确的写法是每次push都进行一次notify(),而不是队列为空时才notify

 ----------------------------------------------------------------------------------------------------------------------------------

pthread_cond_wait总和一个互斥锁结合使用。在调用pthread_cond_wait前要先获取锁。pthread_cond_wait函数执行时先自动释放指定的锁,然后等待条件变量的变化。在函数调用返回之前,自动将指定的互斥量重新锁住。

int pthread_cond_signal(pthread_cond_t * cond);

pthread_cond_signal通过条件变量cond发送消息,若多个消息在等待,它只唤醒一个。pthread_cond_broadcast可以唤醒所有。调用pthread_cond_signal后要立刻释放互斥锁,因为pthread_cond_wait的最后一步是要将指定的互斥量重新锁住,如果pthread_cond_signal之后没有释放互斥锁,pthread_cond_wait仍然要阻塞。


无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race   Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁 (PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁 (pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开 pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。  
   
  激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。

下面是另一处说明:给出了函数运行全过程。 为什么在唤醒线程后要重新mutex加锁?

了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心,也是最难以理解的部分。

首先,让我们考虑以下情况:线程为查看已链接列表而锁定了互斥对象,然而该列表恰巧是空的。这一特定线程什么也干不了 -- 其设计意图是从列表中除去节点,但是现在却没有节点。因此,它只能:

锁定互斥对象时,线程将调用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 调用相当复杂,因此我们每次只执行它的一个操作。

pthread_cond_wait() 所做的 第一件事 就是同时对互斥对象解锁(于是其它线程可以修改已链接列表),并等待条件 mycond 发生(这样当 pthread_cond_wait() 接收到另一个线程的“信号”时,它将苏醒)。现在互斥对象已被解锁,其它线程可以访问和修改已链接列表,可能还会添加项。 【 要求解锁并阻塞是一个原子操作

此时,pthread_cond_wait() 调用还未返回。对互斥对象解锁会立即发生,但等待条件 mycond 通常是一个阻塞操作,这意味着线程将睡眠,在它苏醒之前不会消耗 CPU 周期。这正是我们期待发生的情况。线程将 一直睡眠,直到特定条件发生 ,在这期间不会发生任何浪费 CPU 时间的繁忙查询。从线程的角度来看,它只是在等待 pthread_cond_wait() 调用返回。

现在继续说明,假设另一个线程(称作“2 号线程”)锁定了 mymutex 并对已链接列表添加了一项。在对互斥对象解锁之后,2 号线程会立即调用函数 pthread_cond_broadcast(&mycond)。此操作之后,2 号线程将使所有等待 mycond 条件变量的线程立即苏醒。这意味着第一个线程(仍处于 pthread_cond_wait() 调用中)现在将 苏醒

现在,看一下第一个线程发生了什么。您可能会认为在 2 号线程调用 pthread_cond_broadcast(&mymutex) 之后,1 号线程的 pthread_cond_wait() 会立即返回。不是那样!实际上,pthread_cond_wait() 将执行最后一个操作: 重新锁定 mymutex 。一旦 pthread_cond_wait() 锁定了互斥对象,那么它将返回并允许 1 号线程继续执行。那时,它可以马上检查列表,查看它所感兴趣的更改。


来看一个例子(你是否能理解呢?):

 

In Thread1:

pthread_mutex_lock(&m_mutex);   
pthread_cond_wait(&m_cond,&m_mutex);   
pthread_mutex_unlock(&m_mutex);  

 

In Thread2:

pthread_mutex_lock(&m_mutex);   
pthread_cond_signal(&m_cond);   
pthread_mutex_unlock(&m_mutex);  

 

为什么要与pthread_mutex 一起使用呢? 这是为了应对 线程1在调用pthread_cond_wait()但线程1还没有进入wait cond的状态的时候,此时线程2调用了 cond_singal 的情况。 如果不用mutex锁的话,这个cond_singal就丢失了。加了锁的情况是,线程2必须等到 mutex 被释放(也就是 pthread_cod_wait() 释放锁并进入wait_cond状态 ,此时线程2上锁) 的时候才能调用cond_singal.


pthread_cond_signal即可以放在pthread_mutex_lock和pthread_mutex_unlock之间,也可以放在pthread_mutex_lock和pthread_mutex_unlock之后,但是各有有缺点。

之间:
pthread_mutex_lock
    xxxxxxx
pthread_cond_signal
pthread_mutex_unlock
缺点:在某下线程的实现中,会造成等待线程从内核中唤醒(由于cond_signal)然后又回到内核空间(因为cond_wait返回后会有原子加锁的 行为),所以一来一回会有性能的问题。但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
所以在Linux中推荐使用这种模式。

之后:
pthread_mutex_lock
    xxxxxxx
pthread_mutex_unlock
pthread_cond_signal
优点:不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了
缺点:如果unlock和signal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程),而这在上面的放中间的模式下是不会出现的。



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/509225.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ARP-地址解析协议(在实践中深入理解ARP协议)

在同一个网络(无特别说明,均指以太网络)中进行通信的主机,必须要拥有目标主机的MAC地址才能够正确地将数据发送给目标主机,那么如何知道目标主机的MAC地址呢?可以通过ARP协议。ARP协议就是用来获取目标IP地…

Maven私服

1 Maven私服简介 Maven 私服是一种特殊的Maven远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的远程仓库(中央仓库、其他远程公共仓库)。 1.1 下载构件顺序 建立私服后,当局域网内的用户需要某个构件时&a…

Unity3d--跨平台(一)

转自:https://www.cnblogs.com/murongxiaopifu/p/4211964.html前言: 其实小匹夫在U3D的开发中一直对U3D的跨平台能力很好奇。到底是什么原理使得U3D可以跨平台呢?后来发现了Mono的作用,并进一步了解到了CIL的存在。所以&#xff0…

linux定时任务的用法详解

crontab的基本格式: f1  f2  f3  f4  f5  command 分  时 日  月  周  命令 第一列f1代表分钟1~59:当f1为表示每分钟都要执行;为/n表示每n分钟执行一次;为a-b表示从第a分钟到第b分钟这段时间要执行;为a,…

Unity3d-跨平台(二)

转自:http://www.jiandaima.com/blog/archives/945.html 是如何输出到多平台的? 我的第一篇文章,选择了一个不那么简单的主题,但是是我近期比较感兴趣的。这周,我和一个朋友,谈到了游戏开发和Unity3D&#…

svn冲突解决方案

解决方法 步骤一、清空svn的队列 1、进入到项目的.svn目录中,查看是否存在wc.db文件 C:\Users\Administrator>D:D:\>cd D:\BBK_SVN\I3_TrunkD:\BBK_SVN\I3_Trunk>cd .svnD:\BBK_SVN\I3_Trunk\.svn>dirVolume in drive D has no label.Volume Serial Nu…

redis集群搭建与配置

redis集群搭建与配置

keepalived的安装与添加服务

keepalived的安装与添加服务

Mr. Bender and Square

Description Mr. Bender has a digital table of size n  n, each cell can be switched on or off. He wants the field to have at least c switched on squares. When this condition is fulfilled, Mr Bender will be happy. Well consider the table rows numbered from…

keepalived+nginx保持高可用配置

安装nginx、keepalived nginx安装 keepalived安装与添加服务在/etc/keepalived目录下新建nginx_check.sh(两台服务器都需要) 配置keepalived.conf: #配置邮箱 global_defs {notification_email {# acassenfirewall.loc# failoverfirewall.loc# sysadmin…

nginx+keepalived详细配置信息

Nginx Keepalived 第一步: 下载keepalived地址:http://www.keepalived.org/download.html 解压安装: tar -zxvf keepalived-1.2.18.tar.gz -C /usr/local/ yum install -y openssl openssl-devel(需要安装一个软件包&#xff09…

毕业3年,为何技术能力相差越来越大?

导读:毕业三年,每个人在技术能力跑道上,有了或大或小的差距。有些人永远在重复的劳动,有些人却能从中总结和解决问题。今天我们来探讨下,如何避免让战术上的勤奋掩盖战略上的懒惰,使得真正掌握好的知识点慢…

Periodic Signal

描述 Profess X is an expert in signal processing. He has a device which can send a particular 1 second signal repeatedly. The signal is A0 ... An-1 under n Hz sampling. One day, the device fell on the ground accidentally. Profess X wanted to check whether …

boost stacktrace堆栈打印

在windows下最方便的是minidump,其他2个平台麻烦不少,google-breakpad使用起来又太麻烦. 最近boost1.65版本出了个stacktrace使用起来简单方便,只是无法看实际数据,对于快速定位BUG还是很有帮助的. 要注意的是异常的处理需要写文件,应用重启之后再读取查看~ 用其他应用读取或…

Windows下dump文件生成与分析

一、 生成Dump文件方式 1.1任务管理器 在程序崩溃后,先不关闭程序,在任务管理器中找到该程序对应的进程。右键—>创建转储文件。 此时会在默认的目录下创建出一个dump文件。 可以看出,此种方法只适用于程序崩溃但没有立即自行退出的情…

迪杰斯特拉算法 两点间最短路径的选择

百度首页 登录 注册 新闻网页贴吧知道音乐图片视频地图百科文库进入词条搜索词条帮助首页分类艺术科学自然文化地理生活社会人物经济体育历史特色百科历史上的今天数字博物馆史记2015城市百科二战百科非遗百科用户蝌蚪团燃梦计划百科任务百科商城权威合作合作模式常见问题联系方…

TLS--线程局部存储

转自:https://blog.csdn.net/u013761036/article/details/54960277 这个东西并不陌生了,之前写过了一个关于这个的应用,利用静态TLS姿势实现代码段静态加密免杀或者所谓的加壳思路。地址在这:http://blog.csdn.net/u013761036/article/detai…

向量内积(点乘)和外积(叉乘)概念及几何意义

向量的内积(点乘) 定义 概括地说,向量的内积(点乘/数量积)。对两个向量执行点乘运算,就是对这两个向量对应位一一相乘之后求和的操作,如下所示,对于向量a和向量b: a和b…

SVN分支/合并

转自:https://blog.csdn.net/e3002/article/details/21469437 先说说什么是branch。按照Subversion的说法,一个branch是某个development line(通常是主线也即trunk)的一个拷贝,见下图: branch存在的意义在…

prim算法 求最小生成树

最小生成树Prim算法理解 标签: Prim算法理解最小生成树Prim2014-08-16 18:49 18482人阅读 评论(5) 收藏 举报版权声明:本文为博主原创文章,未经博主允许不得转载。 MST(Minimum Spanning Tree,最小生成树)问…