Linux线程同步(条件变量)

2023-08-09 08:56:37


前言

本篇文章来讲解一下条件变量的使用。

一、条件变量概念

条件变量(Condition Variable)是并发编程中一种线程同步机制,用于实现线程之间的等待和通知机制。它是一种与特定条件相关的线程同步原语。

条件变量用于线程间的协调,允许一个线程在满足某个特定条件之前等待,并在其他线程满足条件后被通知继续执行。它通常与互斥锁(Mutex)结合使用,以提供更精细的线程同步和共享数据的访问控制。

条件变量的基本概念包括以下几个要素:

1.等待和通知:
条件变量提供了等待和通知的机制,等待(Wait)操作用于使线程进入等待状态,直到满足某个特定条件。通知(Signal或Broadcast)操作用于唤醒等待中的线程,告知它们条件已经满足。

2.互斥锁:
在使用条件变量之前,通常需要先获取与之配套的互斥锁,以确保共享数据的互斥访问。互斥锁用于保护与条件相关的共享资源,以防止多个线程同时访问。

3.条件谓词:
条件变量通常与条件谓词(Condition Predicate)一起使用,条件谓词是描述条件是否满足的谓词表达式。在线程等待之前和通知之后,都需要通过条件谓词进行条件的检查。

条件变量的典型用法包括以下步骤:

1.线程获取互斥锁,锁定共享资源。

2.检查条件谓词,判断是否满足等待条件,如果不满足则等待条件变量。

3.线程释放互斥锁并进入等待状态,直到其他线程发出通知。

4.当其他线程满足条件变量的条件并发出通知时,等待中的线程被唤醒。

5.线程重新获取互斥锁,继续执行后续操作。

条件变量的使用可以避免线程的忙等待,提高程序的效率,并且更加灵活地实现线程间的通信和同步。但在使用条件变量时需要注意正确的加锁和解锁的顺序,以避免死锁和竞态条件等并发编程的常见问题。

二、条件变量相关的函数

条件变量在Linux下使用一些常见的函数来实现线程之间的同步和通信。下面是条件变量使用的几个常用函数:

1.pthread_cond_init():用于初始化条件变量。‘

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

参数 cond:指向要初始化的条件变量。
参数 attr:指向条件变量的属性对象,通常传递 NULL。
返回值:成功返回0,失败返回错误码。

2.pthread_cond_destroy():用于销毁条件变量。

int pthread_cond_destroy(pthread_cond_t *cond);

参数 cond:指向要销毁的条件变量。
返回值:成功返回0,失败返回错误码。

3.pthread_cond_wait():用于等待条件变量满足条件。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

参数 cond:指向要等待的条件变量。
参数 mutex:指向与条件变量关联的互斥锁。
返回值:成功返回0,失败返回错误码。
在调用 pthread_cond_wait() 之前,必须先获得与条件变量关联的互斥锁 mutex 的锁,然后该函数会自动释放 mutex 的锁,并让线程进入等待状态,直到被另一个线程通过 pthread_cond_signal() 或 pthread_cond_broadcast() 唤醒。

4.pthread_cond_signal():用于唤醒一个正在等待条件变量的线程。

int pthread_cond_signal(pthread_cond_t *cond);

参数 cond:指向要唤醒的条件变量。
返回值:成功返回0,失败返回错误码。
pthread_cond_signal() 会唤醒等待 cond 条件变量的一个线程,如果没有线程在等待条件变量,则该函数调用没有任何效果。

5.pthread_cond_broadcast():用于唤醒所有正在等待条件变量的线程。

int pthread_cond_broadcast(pthread_cond_t *cond);

参数 cond:指向要广播的条件变量。
返回值:成功返回0,失败返回错误码。
pthread_cond_broadcast() 会唤醒等待 cond 条件变量的所有线程,即广播唤醒所有等待的线程。

三、条件变量模拟生产者消费者模型

示例代码:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

#define BUFFER_SIZE 10

typedef struct {
    int buffer[BUFFER_SIZE];
    int count;
    int in;
    int out;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} Buffer;

Buffer buffer;

void* producer(void* arg) 
{
    int item = 1;
    while (1) 
    {
        pthread_mutex_lock(&buffer.mutex);
        
        // 检查缓冲区是否已满
        while (buffer.count == BUFFER_SIZE) 
        {
            pthread_cond_wait(&buffer.cond, &buffer.mutex);
        }
        
        // 写入数据并更新缓冲区状态
        buffer.buffer[buffer.in] = item;
        buffer.in = (buffer.in + 1) % BUFFER_SIZE;
        buffer.count++;
        printf("生产者生产了:%d\n", item);

        // 唤醒等待的消费者线程
        pthread_cond_signal(&buffer.cond);
        
        pthread_mutex_unlock(&buffer.mutex);
        
        item++;
        sleep(1);
    }
    return NULL;
}

void* consumer(void* arg) 
{
    while (1) 
    {
        pthread_mutex_lock(&buffer.mutex);
        
        // 检查缓冲区是否为空
        while (buffer.count == 0) 
        {
            pthread_cond_wait(&buffer.cond, &buffer.mutex);
        }
        
        // 读取数据并更新缓冲区状态
        int item = buffer.buffer[buffer.out];
        buffer.out = (buffer.out + 1) % BUFFER_SIZE;
        buffer.count--;
        printf("消费者消费了:%d\n", item);
        
        // 唤醒等待的生产者线程
        pthread_cond_signal(&buffer.cond);
        
        pthread_mutex_unlock(&buffer.mutex);
        
        sleep(1);
    }
    return NULL;
}

int main(void)
{

    pthread_t producer_thread, consumer_thread;
    
    // 初始化缓冲区和相关的同步对象
    buffer.count = 0;
    buffer.in = 0;
    buffer.out = 0;
    pthread_mutex_init(&buffer.mutex, NULL);
    pthread_cond_init(&buffer.cond, NULL);

    // 创建生产者线程和消费者线程
    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);
    
    // 等待线程结束
    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);
    
    // 销毁同步对象
    pthread_mutex_destroy(&buffer.mutex);
    pthread_cond_destroy(&buffer.cond);

    

    return 0;
}

运行效果:
在这里插入图片描述

四、使用条件变量的好处

1.避免资源浪费:当条件不满足时,线程可以调用pthread_cond_wait函数来等待条件满足。在等待期间,该线程会释放持有的锁,允许其他线程获得锁并继续执行临界区代码。这样可以避免空转和资源浪费。

2.避免竞争条件:条件变量的使用可以帮助避免竞争条件的发生。线程间的协作通过条件变量来实现,确保在满足特定条件之前,线程不会执行关键代码段。

3.提高并发性:条件变量使得线程能够在需要等待条件满足时休眠,而不是通过忙等待消耗处理器资源。这样可以提高系统的并发性和效率。

4.简化同步逻辑:使用条件变量可以简化同步逻辑,使代码更加清晰易懂。条件变量提供了一种灵活而有效的机制来控制线程的行为,使得编写正确的多线程代码变得更加容易。

总结

本篇文章主要讲解了条件变量的概念和条件变量的相关函数和使用条件变量模拟生产者消费者模型。

更多推荐

新手学习:ArcGIS 提取SHP 路网数据、节点

新手学习:ArcGIS提取SHP路网数据、节点参考连接OSM路网提取道路节点ArcGIS:如何创建地理数据库、创建要素类数据集、导入要素类、表?1.导入开源路网SHP文件2.在交点处打断路网数据未打断路网数据有一些路径很长,并且和多个路径存在交点,所以需要打断相交线。打开要编辑的shp数据,工具栏中,编辑器-开始编辑,

PHP 如何创建一个 composer 包 并在 项目中使用自己的 composer sdk 包

第一步创建一个composerSDK项目创建一个composer.json文件或使用命令(如果不清楚怎么弄直接跳过即可,一般都会默认配置)composerinit这是生成的composer.json文件将自己要使用的包添加到require中,如果没有require则自己添加(composer文件中必须用双引号不能使用单

加密货币交易所偿付能力的零知识证明

如何检测下一个FTX和Mt.Gox加密货币交易所FTX的内爆导致数十亿客户资金流失,这是加密货币历史上交易所破产的最新例子。历史可以追溯到2014年,当时处理70%比特币交易的历史最悠久、规模最大的交易所Mt.Gox丢失了用户的850,000个比特币。如今,许多用户更喜欢将他们的加密货币资产存储在集中式交易所中,以便于

区块链交易平台开发流程

随着区块链技术的日益发展,越来越多的金融机构和创业公司开始探索开发区块链交易平台的潜力。以下是一篇关于区块链交易平台开发流程的指南。一、理解区块链技术在开发区块链交易平台之前,必须深入理解区块链技术的内在机制和原理。区块链是一种分布式数据库,通过去中心化和去信任的方式维护可靠的数据记录,使得任何达成一致的双方能够直接进

springboot和vue:三、web入门(spring-boot-starter- web+控制器+路由映射+参数传递)

spring-boot-starter-webSpringBoot将传统Web开发的mvc、json、tomcat等框架整合,提供了spring-boot-starter-web组件,简化了Web应用配置。创建SpringBoot项目勾选SpringWeb选项后,会自动将spring-boot-starter-web组

Android 回声消除

Android回声消除前言在语音聊天、语音通话、互动直播、语音转文字类应用或者游戏中,需要采集用户的麦克风音频数据,然后将音频数据发送给其它终端或者语音识别服务。如果直接使用采集的麦克风数据,就会存在回音问题。所谓回音就是在语音通话过程中,如果用户开着扬声器,那么自己讲话的声音和对方讲话的声音(即是扬声器的声音)就会混

c++基础:new函数

new函数new是用于动态分配内存的操作符。它用于在堆内存中创建一个新的对象或数据结构,并返回一个指向该内存的指针。这是C++中进行动态内存分配的主要方式之一,通常与delete操作符一起使用来释放先前分配的内存。以下是使用new操作符的一些示例:动态分配一个整数,并将其赋值给指针:int*pInt=newint;*p

Python基础运算分享

Python的运算符和其他语言类似(我们暂时只了解这些运算符的基本用法,方便我们展开后面的内容,高级应用暂时不介绍)数学运算>>>print1+9#加法>>>print1.3-4#减法>>>print3*5#乘法>>>print4.5/1.5#除法>>>print3**2#乘方>>>print10%3#求余数判断判断是

抖音开网店无货源怎么找

随着社交媒体的快速发展,抖音已经成为了一种极具潜力的电商平台。许多人想要利用这个平台开设网店,但是其中很多人面临的问题是如何找到货源。无货源的抖音网店经营固然具有一定的难度,但并非不可行。以下是一些帮助你在抖音开网店无货源的方法。代销合作:寻找与制造商或批发商的代销合作是一种常见的方式。你可以与他们签订协议,销售他们的

1787_函数指针的使用

全部学习汇总:GitHub-GreyZhang/c_basic:littlebitsofc.前阵子似乎写了不少错代码,因为对函数指针的理解还不够。今天晚上似乎总算是梳理出了一点眉目,在先前自己写过的代码工程中做一下测试。先前实现过一个归并排序算法,算法函数的一个传入参数是指向一个比较功能函数的指针。当时进行代码实现的时

数据治理-数据存储和操作-数据架构类型

数据库可以分为集中式数据库和分布式数据。集中式系统管理单一数据库,而分布式系统管理多个系统上的多个数据库。分布式系统组件可以根据组件系统的自治性分为两类:联邦的和非联邦的。集中式数据库集中式数据库将所有数据存放在一个地方的一套系统中,所有用户连接到这套系统进行数据访问。对某些访问受限的数据来说,集中化可能是理想的,但对

热文推荐