Linux线程(十一)线程互斥锁-条件变量详解

简介: Linux线程(十一)线程互斥锁-条件变量详解

条件变量是线程可用的另一种同步机制。条件变量用于自动阻塞线程,知道某个特定事件发生或某个条件满足为止,通常情况下,条件变量是和互斥锁一起搭配使用的。使用条件变量主要包括两个动作:

一个线程等待某个条件满足而被阻塞;
另一个线程中,条件满足时发出“信号”。
为了说明这个问题,来看一个没有使用条件变量的例子,生产者---消费者模式,生产者这边负责生产产品、而消费者负责消费产品,对于消费者来说,没有产品的时候只能等待产品出来,有产品就使用它。这里我们使用一个变量来表示这个这个产品,生产者生产一件产品变量加 1,消费者消费一次变量减 1,

示例代码如下所示:

//示例代码 12.3.1 生产者---消费者示例代码

include

include

include

include

include

static pthread_mutex_t mutex;
static int g_avail = 0;

/ 消费者线程 /
static void consumer_thread(void arg)
{
for ( ; ; ) {
pthread_mutex_lock(&mutex);//上锁

         while (g_avail > 0)
             g_avail--; //消费
         pthread_mutex_unlock(&mutex);//解锁
     }
 return (void *)0; 
AI 代码解读

}

/ 主线程(生产者) /
int main(int argc, char *argv[])
{
pthread_t tid;
int ret;

     /* 初始化互斥锁 */
     pthread_mutex_init(&mutex, NULL);

     /* 创建新线程 */
     ret = pthread_create(&tid, NULL, consumer_thread, NULL);
     if (ret) {
             fprintf(stderr, "pthread_create error: %s\\n", strerror(ret));
             exit(-1);
     }

     for ( ; ; )
     {
             pthread_mutex_lock(&mutex);//上锁
             g_avail++; //生产
             pthread_mutex_unlock(&mutex);//解锁
     }
     exit(0);
AI 代码解读

}

此代码中,主线程作为“生产者”,新创建的线程作为“消费者”,运行之后它们都回处于死循环中,所以代码中没有加入销毁互斥锁、等待回收新线程相关的代码,进程终止时会自动被处理。

上述代码虽然可行,但由于新线程中会不停的循环检查全局变量 g_avail 是否大于 0,故而造成 CPU 资源的浪费。采用条件变量这一问题就可以迎刃而解!条件变量允许一个线程休眠(阻塞等待)直至获取到另一个线程的通知(收到信号)再去执行自己的操作,譬如上述代码中,当条件 g_avail > 0 不成立时,消费者线程会进入休眠状态,而生产者生成产品后(g_avail++,此时 g_avail 将会大于 0),向处于等待状态的线程发出“信号”,而其它线程收到“信号”之后,便会被唤醒!

💡 Tips:这里提到的信号并不是第八章内容所指的信号,需要区分开来!

前面说到,条件变量通常搭配互斥锁来使用,是因为条件的检测是在互斥锁的保护下进行的,也就是说条件本身是由互斥锁保护的,线程在改变条件状态之前必须首先锁住互斥锁,不然就可能引发线程不安全的问题。

  1. 条件变量初始化
    条件变量使用 pthread_cond_t 数据类型来表示,类似于互斥锁,在使用条件变量之前必须对其进行初始化。初始化方式同样也有两种:使用宏 PTHREAD_COND_INITIALIZER 或者使用函数 pthread_cond_init(),使用宏的初始化方法与互斥锁的初始化宏一样,这里就不再重述!譬如:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_init()

函数原型如下所示:

include

int pthread_cond_destroy(pthread_cond_t cond);
int pthread_cond_init(pthread_cond_t
cond, const pthread_condattr_t *attr);
同样,使用这些函数需要包含头文件,使用 pthread_cond_init()函数初始化条件变量,当不再使用时,使用 pthread_cond_destroy()销毁条件变量。

参数 cond 指向 pthread_cond_t 条件变量对象,对于 pthread_cond_init()函数,类似于互斥锁,在初始化条件变量时设置条件变量的属性,参数 attr 指向一个 pthread_condattr_t 类型对象,pthread_condattr_t 数据类型用于描述条件变量的属性。可将参数 attr 设置为 NULL,表示使用属性的默认值来初始化条件变量,与使用 PTHREAD_COND_INITIALIZER 宏相同。

函数调用成功返回 0,失败将返回一个非 0 值的错误码。

对于初始化与销毁操作,有以下问题需要注意:

在使用条件变量之前必须对条件变量进行初始化操作,使用 PTHREAD_COND_INITIALIZER 宏或者函数 pthread_cond_init()都行;
对已经初始化的条件变量再次进行初始化,将可能会导致未定义行为;
对没有进行初始化的条件变量进行销毁,也将可能会导致未定义行为;
对某个条件变量而言,仅当没有任何线程等待它时,将其销毁才是最安全的;
经 pthread_cond_destroy()销毁的条件变量,可以再次调用 pthread_cond_init()对其进行重新初始化。

  1. 通知和等待条件变量
    条件变量的主要操作便是发送信号(signal)和等待。发送信号操作即是通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变,这些处于等待状态的线程收到通知之后便会被唤醒,唤醒之后再检查条件是否满足。等待操作是指在收到一个通知前一直处于阻塞状态。

函数 pthread_cond_signal()和 pthread_cond_broadcast()均可向指定的条件变量发送信号,通知一个或多个处于等待状态的线程。调用 pthread_cond_wait()函数是线程阻塞,直到收到条件变量的通知。

pthread_cond_signal()和 pthread_cond_broadcast()函数原型如下所示:

include

int pthread_cond_broadcast(pthread_cond_t cond);
int pthread_cond_signal(pthread_cond_t
cond);
使用这些函数需要包含头文件,参数 cond 指向目标条件变量,向该条件变量发送信号。调用成功返回 0;失败将返回一个非 0 值的错误码。

pthread_cond_signal()和 pthread_cond_broadcast()的区别在于:二者对阻塞于 pthread_cond_wait()的多个线程对应的处理方式不同,pthread_cond_signal()函数至少能唤醒一个线程,而 pthread_cond_broadcast()函数则能唤醒所有线程。使用pthread_cond_broadcast()函数总能产生正确的结果,唤醒所有等待状态的线程,但函数 pthread_cond_signal()会更为高效,因为它只需确保至少唤醒一个线程即可,所以如果我们的程序当中,只有一个处于等待状态的线程,使用pthread_cond_signal()更好,具体使用哪个函数根据实际情况进行选择!

目录
打赏
0
0
0
0
118
分享
相关文章
|
9天前
|
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
38 17
|
18天前
|
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
50 26
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
54 6
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
62 0
Linux C/C++之线程基础
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
2月前
|
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
235 2
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
2月前
|
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
60 10
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等