多线程---条件变量

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

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

复制代码
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,一经查实,立即删除!

相关文章

ngnix安装

一、安装Nginx: 1 : wget下载: http://nginx.org/download/nginx-1.4.2.tar.gz 2 : 进行安装: tar -zxvf nginx-1.6.2.tar.gz 3 : 下载锁需要的依赖库文件: yum install pcre yum install pcre-devel yum install zlib yum install zl…

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

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

Maven私服

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

nginx配置文件中参数的作用

####默认的nobody,没有访问目录权限,然后指定有权限的用户 ####user nobody; ####一般一个进程足够了,你可以把连接数设得很大。 ####如果有SSL、gzip这些比较消耗CPU的工作,而且是多核CPU的话,可以设为和CPU的数量一…

TCP/IP协议--ARP协议(有了IP地址为什么还需要ARP协议)

首先我们需要先大致了解一下MAC地址,MAC(Media Access Control, 介质访问控制)地址是烧录在Network Interface Card(网卡,NIC)里的,也叫硬件地址,是由48比特长(6字节),16进制的数字组成.0-23位叫做组织唯一标志符(organizationally unique &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&#…

lua的作用

轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。 可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C)提供这些功能,Lua可以使用它们,就像是本来就内…

Almost Arithmetical Progression

Description Gena loves sequences of numbers. Recently, he has discovered a new type of sequences which he called an almost arithmetical progression. A sequence is an almost arithmetical progression, if its elements can be represented as: a1  p, where p i…

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集群搭建与配置

VS编译快捷键设置

1.编译当前文件----AltZ(生成.编译); 2.编译当前项目----AltA(生成.仅生成项目); 3.链接当前项目----AltX(生成.链接); 4.生成选定内容----AltD(生成.生成选定内容);

keepalived的安装与添加服务

keepalived的安装与添加服务

做一个“多人在线编辑器”,你会怎么开始

看似只是一个简单的问题,但是其中却隐含了非常多的知识,对于“多人在线编辑器”这么一个产品来说,如果让你来负责设计并开发,你会怎么去开始一步步展开工作,其中主要考察的并不是让你迅速的不假思索的说运用什么技术&a…

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…

nginx_keepalived配置(转载保存)

文章链接: https://blog.csdn.net/yabingshi_tech/article/details/52038332

IT技术网站

GitChat : http://gitbook.cn/ CSDN: https://blog.csdn.net/nav/career 知乎: https://www.zhihu.com/ 简书: https://www.jianshu.com/ 程序师: http://www.techug.com/ 酷壳:https://www.baidu.com/link…

A Simple Job

描述 Institute of Computational Linguistics (ICL), Peking University is an interdisciplinary institute of science and liberal arts, it focuses primarily on the fundamental researches and applications of language information processing. The research of ICL …

keepalived+nginx保持高可用配置

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