互斥量、条件变量与pthread_cond_wait()函数的使用,详解

2024-03-16 00:32

本文主要是介绍互斥量、条件变量与pthread_cond_wait()函数的使用,详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 首先pthread_cond_wait 的定义是这样的

The pthread_cond_wait()and pthread_cond_timedwait()functions are used to block on a condition variable. They are called with mutexlocked by the calling thread or undefined behaviour will result.

These functions atomically release mutexand cause the calling thread to block on the condition variable cond;atomically here means "atomically with respect to access by anotherthread to the mutex and then the condition variable". That is, ifanother thread is able to acquire the mutex after the about-to-blockthread has released it, then a subsequent call to pthread_cond_signal()or pthread_cond_broadcast()in that thread behaves as if it were issued after the about-to-block thread has blocked.

2. 由上解释可以看出,pthread_cond_wait() 必须与pthread_mutex 配套使用。

pthread_cond_wait()函数一进入wait状态就会自动release mutex.

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状态 并自动释放mutex) 的时候才能调用cond_singal.

3. pthread_cond_wait() 一旦wait成功获得cond 条件的时候会自动 lock mutex.

这就会出现另一个问题。这是因为

The pthread_cond_wait()and pthread_cond_timedwait()is a cancellation point.

In Thread3:

pthread_cancel(&m_thread);

pthread_join();

因为pthread_cond_wait()and pthread_cond_timedwait()   是线程退出点函数,因此在Thread3中

可以调用pthread_cancel()来退出线程1。那样显然线程1会在pthread_cond_wait(&m_cond,&m_mutex);    和pthread_mutex_unlock(&m_mutex); 之间退出,    pthread_cond_wait()函数返回后自动lock住了mutex,这个时候线程1退出(并没有运行到pthread_mutex_unlock()),如果Thread2这个时候就再也得不到lock状态了。

通常解决这个问题的办法如下

voidcleanup(void*arg)
{
   pthread_mutex_unlock(&mutex);
}
void* thread1(void* arg)
{
    pthread_cleanup_push(cleanup, NULL); // thread cleanup handler
    pthread_mutex_lock(&mutex);
   pthread_cond_wait(&cond, &mutex);
   pthread_mutex_unlock(&mutex);
   pthread_cleanup_pop(0);
}




LINUX环境下多线程编程肯定会遇到需要条件变量的情况,此时必然要使用pthread_cond_wait()函数。但这个函数的执行过程比较难于理解。
    pthread_cond_wait()的工作流程如下(以MAN中的EXAMPLE为例):
       Consider two shared variables x and y, protected by the mutex mut, and a condition vari-
       able cond that is to be signaled whenever x becomes greater than y.

              int x,y;
              pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
              pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

       Waiting until x is greater than y is performed as follows:

              pthread_mutex_lock(&mut);
              while (x <= y) {
                      pthread_cond_wait(&cond, &mut);
              }
              /* operate on x and y */
              pthread_mutex_unlock(&mut);

       Modifications on x and y that may cause x to become greater than y should signal the con-
       dition if needed:

              pthread_mutex_lock(&mut);
              /* modify x and y */
              if (x > y) pthread_cond_broadcast(&cond);
              pthread_mutex_unlock(&mut);

     这个例子的意思是,两个线程要修改X和Y的值,第一个线程当X<=Y时就挂起,直到X>Y时才继续执行(由第二个线程可能会修改X,Y的值,当X>Y时唤醒第一个线程),即首先初始化一个普通互斥量mut和一个条件变量cond。之后分别在两个线程中分别执行如下函数体:

               pthread_mutex_lock(&mut);
              while (x <= y) {
                      pthread_cond_wait(&cond, &mut);
              }
              /* operate on x and y */
              pthread_mutex_unlock(&mut);

和:       pthread_mutex_lock(&mut);
              /* modify x and y */
              if (x > y) pthread_cond_signal(&cond);
              pthread_mutex_unlock(&mut);

    其实函数的执行过程非常简单,在第一个线程执行到pthread_cond_wait(&cond,&mut)时,此时如果X<=Y,则此函数就将mut互斥量解锁,再将cond条件变量加锁,此时第一个线程挂起(不占用任何CPU周期)。
    而在第二个线程中,本来因为mut被第一个线程锁住而阻塞,此时因为mut已经释放,所以可以获得锁mut,并且进行修改X和Y的值,在修改之后,一个IF语句判定是不是X>Y,如果是,则此时pthread_cond_signal()函数会唤醒第一个线程,并在下一句中释放互斥量mut。然后第一个线程开始从pthread_cond_wait()执行,首先要再次锁mut, 如果锁成功,再进行条件的判断(至于为什么用WHILE,即在被唤醒之后还要再判断,后面有原因分析),如果满足条件,则被唤醒进行处理,最后释放互斥量mut

   至于为什么在被唤醒之后还要再次进行条件判断(即为什么要使用while循环来判断条件),是因为可能有“惊群效应”。有人觉得此处既然是被唤醒的,肯定是满足条件了,其实不然。如果是多个线程都在等待这个条件,而同时只能有一个线程进行处理,此时就必须要再次条件判断,以使只有一个线程进入临界区处理。对此,转来一段:

引用下POSIX的RATIONALE: 

Condition Wait Semantics 

It is important to note that when pthread_cond_wait() andpthread_cond_timedwait() return without error, the associated predicatemay still be false. Similarly, when pthread_cond_timedwait() returnswith the timeout error, the associated predicate may be true due to anunavoidable race between the expiration of the timeout and thepredicate state change. 

The application needs to recheck the predicate on any return because itcannot be sure there is another thread waiting on the thread to handlethe signal, and if there is not then the signal is lost. The burden ison the application to check the predicate. 

Some implementations, particularly on a multi-processor, may sometimescause multiple threads to wake up when the condition variable issignaled simultaneously on different processors. 

In general, whenever a condition wait returns, the thread has tore-evaluate the predicate associated with the condition wait todetermine whether it can safely proceed, should wait again, or shoulddeclare a timeout. A return from the wait does not imply that theassociated predicate is either true or false. 

It is thus recommended that a condition wait be enclosed in the equivalent of a "while loop" that checks the predicate. 

从上文可以看出:
1,pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程. 
2,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐此处使用while循环.

       其实说白了很简单,就是pthread_cond_signal()也可能唤醒多个线程,而如果你同时只允许一个线程访问的话,就必须要使用while来进行条件判断,以保证临界区内只有一个线程在处理。

这篇关于互斥量、条件变量与pthread_cond_wait()函数的使用,详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/813774

相关文章

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

使用Python合并 Excel单元格指定行列或单元格范围

《使用Python合并Excel单元格指定行列或单元格范围》合并Excel单元格是Excel数据处理和表格设计中的一项常用操作,本文将介绍如何通过Python合并Excel中的指定行列或单... 目录python Excel库安装Python合并Excel 中的指定行Python合并Excel 中的指定列P

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过