Linux线程同步机制之条件变量

news2024/11/17 3:32:07

线程同步的概念:

线程同步是多线程编程中的一个重要概念,它确保了多个线程在访问共享资源时能够协调一致,避免出现竞态条件、数据不一致或其他同步相关的问题。线程同步的关键在于控制多个线程的执行顺序和时机。

线程--条件变量的典型应用场景:

生产者-消费者问题

生产者线程生产数据,消费者线程消费数据。生产者在数据未消费完时可能需要等待,消费者在没有数据可消费时也需要等待。

a.组件:

仓库:用于存储产品,是生产者和消费者共享的资源。

生产者线程:负责生产产品并将其放入仓库。

消费者线程:从仓库中取出产品并进行消费

b.原理:

生产者线程

1.生产产品。

2.在仓库未满的情况下,将产品放入仓库。

3.如果仓库已满,生产者线程需要等待,直到消费者线程消费了某些产品,腾出空间。

消费者线程

1.消费产品。

2.在仓库不为空的情况下,从仓库取出产品进行消费。

3.如果仓库为空,消费者线程需要等待,直到生产者线程生产了新的产品

读者-写者问题

多个读者可以同时读取数据,但写者需要独占访问。

计数器问题

多个线程需要对同一个计数器进行递增操作,需要同步以保证计数的准确性

条件变量(Condition Variables)

条件变量是线程同步中的一种机制,它允许线程在某些条件不满足时挂起(即暂停执行),直到其他线程更改了条件并发出信号。条件变量通常与互斥锁结合使用,以确保线程安全

条件变量的主要特点:

  1. 等待条件变量的线程会释放互斥锁并挂起:当线程等待条件变量时,它必须首先拥有相关的互斥锁,然后释放该锁,进入等待状态。
  2. 条件变量被唤醒后需要重新获取互斥锁:当等待条件变量的线程被唤醒(通过信号),它将尝试重新获取之前释放的互斥锁。

条件变量原理:

step 1:消费者线程判断消费条件是否满足(仓库是否有产品),如果有产品则可以消费,然后解锁
step 2 :当条件满足时(仓库产品数量为0),则调用 pthread_cond_wait 函数,这个函数具体做的事
情如下:
在线程睡眠之前,对互斥锁解锁
让线程进入到睡眠状态
等待条件变量收到信号时,该函数重新竞争锁,并获取锁
step 3 :重新判断条件是否满足,如果满足,则继续调用 pthread_cond_wait 函数
step 4 :唤醒后,从 pthread_cond_wait 返回,条件不满足,则正常消费产品
step 5 :释放锁,整个过程结束

条件变量的作用:

1.减少CPU资源消耗,通过允许线程在条件不满足时挂起,而不是忙等待,从而减少CPU资源的消耗。

2.实现阻塞式同步,条件变量提供了一种机制,允许线程在未达到特定条件时阻塞,而不是不断地检查条件

3.实现复杂的同步逻辑,条件变量可以用于实现复杂的线程同步逻辑,如生产者-消费者问题

条件变量初始化:

条件变量的初始化是在使用条件变量之前必须进行的步骤

1.静态初始化

静态初始化是在编译时进行的,适用于在程序开始执行之前就已经确定了条件变量的使用场景

#include <pthread.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2.动态初始化

动态初始化是在程序执行过程中进行的,提供了更多的灵活性,比如可以设置条件变量的属性

函数头文件:
#include <pthread.h>

函数原型:
int pthread_cond_init(pthread_cond_t *restrict cond,
           const pthread_condattr_t *restrict attr);

函数参数:
cond:指向 pthread_cond_t 类型变量的指针,这个变量用于存储条件变量的状态。
attr:指向 pthread_condattr_t 类型变量的指针,定义了条件变量的属性。如果不需要特殊属性,可以传递 NULL

函数返回值:
成功:返回0,表示函数调用成功
失败: 返回错误码,以指示失败原因

错误码:
EAGAIN:系统资源不足(除了内存)
ENOMEM:初始化条件变量的内存不足
EINVAL:提供的属性无效

3.销毁条件变量

pthread_cond_destroy() 函数用于销毁先前初始化的条件变量,释放与条件变量关联的任何资源。当你的程序不再需要使用某个条件变量时,应该调用此函数进行清理

如果条件变量是通过 PTHREAD_COND_INITIALIZER 静态初始化的,则无需调用 pthread_cond_destroy(),因为静态初始化的条件变量在程序结束时会自动释放资源

函数头文件:
#include <pthread.h>

函数原型:
int pthread_cond_destroy(pthread_cond_t *cond);

函数参数:
cond:指向 pthread_cond_t 类型变量的指针,表示要销毁的条件变量。

函数返回值:
成功:返回0,表示函数调用成功
失败:返回错误码,以指示错误

错误码:
EBUSY:条件变量正在被使用,无法销毁。
EINVAL:指定的条件变量未被正确初始化,或者指向条件变量的指针无效。
EPERM:条件变量处于一个不一致的状态,例如,它可能与一个正在运行的线程状态相关联,而该线程异常终止了并且没有被join

 条件变量的信号通知函数:

pthread_cond_signal() 函数

pthread_cond_signal() 函数用于在 POSIX 线程编程中唤醒正在等待指定条件变量的至少一个线程。当条件变量被某个线程等待时,可以由另一个线程调用 pthread_cond_signal() 来唤醒等待的线程之一。

函数头文件:
#include <pthread.h>

函数原型:
int pthread_cond_signal(pthread_cond_t *cond);

函数参数:
cond:指向 pthread_cond_t 类型的变量的指针,该变量是之前已经初始化过的条件变量

函数返回值:
成功:返回0,表示函数调用成功
失败: 返回错误码,具体错误码会指出失败的原因

错误码:
EINVAL:提供的条件变量未正确初始化或指针无效

 注意事项:

pthread_cond_signal() 只会唤醒一个等待指定条件变量的线程。如果多个线程都在等待,将随机唤醒其中一个线程。

当你只需要通知一个线程,或者所有等待线程执行相同任务时,可以使用 pthread_cond_signal()

pthread_cond_broadcast() 函数

pthread_cond_broadcast() 函数用于唤醒所有等待指定条件变量的线程,这个函数是 POSIX 线程(pthread)库中条件变量操作的一部分,它允许一个线程通知多个其他线程关于某个条件的变化。

函数头文件:
#include <pthread.h>

函数原型:
int pthread_cond_broadcast(pthread_cond_t *cond);

函数参数:
cond:指向 pthread_cond_t 类型的变量的指针,该变量是之前已经初始化过的条件变量

函数返回值:
成功:返回0,表示函数调用成功
失败:返回错误码,具体错误码会指出失败的原因

错误码:
EINVAL:提供的条件变量未正确初始化或指针无效

 注意事项:

当有多个线程等待同一个资源,并且该资源可以被任意一个线程消费时,生产者线程可以调用 pthread_cond_broadcast() 来唤醒所有等待的消费者线程。

pthread_cond_broadcast() 会唤醒所有等待指定条件变量的线程。这在有多个线程等待同一个条件变量时非常有用。

pthread_cond_wait() 函数

pthread_cond_wait() 函数用于使当前线程挂起等待,直到其他线程对相同的条件变量发出信号(通过 pthread_cond_signal()pthread_cond_broadcast())。

函数头文件;
#include <pthread.h>

函数原型:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

函数参数:
cond:指向 pthread_cond_t 类型的变量的指针,该变量是之前已经初始化过的条件变量
mutex:指向 pthread_mutex_t 类型的变量的指针,该变量是与条件变量配合使用的互斥锁

函数返回值:
成功:返回0,表示函数调用成功
失败:返回错误码,具体的错误码指示失败原因

 条件变量与互斥锁的关联:

条件变量通常与互斥锁结合使用,以确保线程在检查条件和等待条件变量时数据的一致性和防止竞态条件

  1. 先获得锁:在对共享资源进行操作或检查条件之前,线程必须先获取关联的互斥锁。

  2. 检查条件:线程在获取互斥锁后会检查自己等待的条件是否满足。

  3. 调用 pthread_cond_wait()

    a.如果条件不满足,线程会调用 pthread_cond_wait() 函数挂起等待条件变量的信号。b.在等待期间,pthread_cond_wait() 函数会自动释放互斥锁,让其他线程有机会执行并改变条件。
  4. 阻塞线程:线程在 pthread_cond_wait() 调用中阻塞,直到另一个线程向相同的条件变量发出信号。

  5. 唤醒线程:当其他线程改变了条件并发出 pthread_cond_signal() 或 pthread_cond_broadcast() 信号后,等待的线程可能会被唤醒

  6. 重新竞争锁:被唤醒的线程会重新尝试获取之前释放的互斥锁。

  7. 重新获得锁:在成功获取互斥锁之后,线程再次检查条件是否满足。

  8. pthread_cond_wait() 函数返回

    a.如果线程被唤醒并且已经重新获得互斥锁,pthread_cond_wait() 函数会返回。b.线程此时会再次检查条件是否满足,如果满足则继续执行;如果不满足,则线程可能需要再次等待。
  9. 操作共享资源:如果条件满足,线程就可以对共享资源进行操作。

  10. 释放锁:操作完成后,线程释放互斥锁,以便其他线程可以获取锁并访问共享资源

 在生产者-消费者问题中使用条件变量:

生产者-消费者问题中,如果没有条件变量,生产者和消费者线程可能需要不断检查共享资源的状态,这会导致大量的CPU周期浪费在无效的检查上

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

#define MAX_GLOBAL 10000  // 定义生产者和消费者的最大产品数量

int global = 0;  // 全局计数器,用于生产的产品编号
int consumer_count = 0;  // 已消费的产品数量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 互斥锁,用于保护共享资源
pthread_cond_t cond_prod = PTHREAD_COND_INITIALIZER;  // 生产者条件变量
pthread_cond_t cond_cons = PTHREAD_COND_INITIALIZER;  // 消费者条件变量

void *producer(void *arg) {
    int count = atoi((char*)arg);
    for(int i = 0; i < count; i++) {
        pthread_mutex_lock(&mutex);
        while (global == MAX_GLOBAL) {  // 如果产品已满,等待消费者消费
            pthread_cond_wait(&cond_prod, &mutex);
        }
        printf("[%ld] Production product %d\n", pthread_self(), global++);
        pthread_cond_signal(&cond_cons);  // 通知消费者有新产品
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}

void *consumer(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);
        while (global == 0) {  // 如果没有产品,等待生产者生产
            pthread_cond_wait(&cond_cons, &mutex);
        }
        printf("Consumer consumed product %d\n", global--);
        consumer_count++;
        if (consumer_count == MAX_GLOBAL) {  // 如果已消费的产品数量达到最大值,退出
            pthread_cond_signal(&cond_prod);  // 通知生产者停止生产
            pthread_mutex_unlock(&mutex);
            break;
        }
        pthread_cond_signal(&cond_prod);  // 通知生产者有空间生产新产品
        pthread_mutex_unlock(&mutex);
        sleep(1);  // 模拟消费时间
    }
    pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Too few parameters!\n");
        exit(EXIT_FAILURE);
    }

    int product_number = 0;
    pthread_t tid_producer, tid_consumer;

    for (int i = 1; i < argc; i++) {
        product_number += atoi(argv[i]);
        if (pthread_create(&tid_producer, NULL, producer, (void*)argv[i])) {
            printf("producer %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    if (pthread_create(&tid_consumer, NULL, consumer, NULL)) {
        printf("consumer %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    pthread_join(tid_producer, NULL);
    pthread_join(tid_consumer, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond_prod);
    pthread_cond_destroy(&cond_cons);

    return 0;
}

结语:

无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力

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

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

相关文章

简单的springboot 编写Socket服务接口

简单的springboot 编写Socket服务接口 1.需求 我们项目中有部分老接口为票据接口&#xff0c;其中实现为java socket形式进行实现&#xff0c;但是其中大部分信息都是原始公司封装的包进行实现的&#xff0c;想要修改非常费劲&#xff0c;所以此处简单了解了一下socket&#…

卫星导航定位原理学习(三)

GNSS信号体制及其性能分析 GNSS信号体制直接影响卫星导航系统的性能&#xff0c;是卫星导航系统设计的重要内容。卫星导航信号体制主要包括信号频率、信号结构、导航电文3部分。其中信号结构又包括调制波形、频率带宽、扩频码码长、码速率、码结构、信号功率等内容。导航电文设…

25 Vue3之如何开发移动端并适配

开发移动端最主要的就是适配各种手机 vw vh是相对viewport 视口的单位&#xff0c;配合meta标签可以直接使用&#xff0c;无需计算 1vw1/100视口宽度 1vh1/100视口高度 当前屏幕视口是375像素&#xff0c;1vw就是3.75px postCss 提供了 把Css 转换AST的能力&#xff0c;类…

LeetCode --- 416周赛

题目列表 3295. 举报垃圾信息 3296. 移山所需的最少秒数 3297. 统计重新排列后包含另一个字符串的子字符串数目 I 3298. 统计重新排列后包含另一个字符串的子字符串数目 II 一、举报垃圾信息 直接用哈希表统计bannedWords中的单词&#xff0c;遍历message中出现的垃圾信息…

WiFi无线连接管理安卓设备工具:WiFiADB

介绍 WiFi ADB 使您能够通过 WiFi TCP/IP 连接直接在设备上轻松调试和测试 Android 应用&#xff0c;无需使用 USB 数据线。在启用 WiFi 上的 ADB 后&#xff0c;打开控制台将电脑连接到设备。 手机和电脑在同一个WiFi然后电脑上运行adb connect x.x.x.x:x命令即可 下载 谷…

Go语言开发后台框架不能只有CRUD还需有算法集成基础功能-GoFly框架集成了自然语言处理(NLP)分词、关键词提取和情感分析

前言 Go语言开发框架&#xff0c;我们要把Go的优势体现在框架中&#xff0c;不仅CRUD常规操作&#xff0c;还要把常用即有算力自己集成到框架中&#xff0c;而不是去购买第三方提供服务接口。作为开发者可以拓宽自己代码面&#xff0c;获取更多成就感&#xff0c;同时也提供自…

戴尔PowerEdge R840服务器亮黄灯 不开机

最近接修到一台东莞用户的DELL PowerEdge R840 服务器因为意外断电后&#xff0c;无法正常开机的问题&#xff0c; 大概故障现象是 插上电源线 按卡机按钮无响应&#xff0c;无法开机&#xff0c;无显示输出&#xff0c;工程师到现场检修&#xff0c;经过idrac中日志分析&#…

商标是什么?为何对企业至关重要?

商标作为企业的核心标识&#xff0c;不仅是区分商品与服务的关键&#xff0c;更是企业品牌塑造、市场区分和消费者信任建立的基石。那么&#xff0c;商标究竟是什么&#xff1f;它又为何对企业如此重要呢&#xff1f; 商标的定义及类型 商标&#xff08;Trademark&#xff09;…

Python获取百度翻译的两种方法

一、引言 百度是我们常用的搜索工具&#xff0c;其翻译是与爱词霸合作&#xff0c;总体看其反应速度较快&#xff0c;可以作为项目中重要的翻译工具。根据大家的需要&#xff0c;现提供两种Python获取百度翻译的两种办法&#xff1a; 二、requests法 我们引用requests模块&a…

构建5G-TSN测试平台:架构与挑战

论文标题&#xff1a;Building a 5G-TSN Testbed: Architecture and Challenges 作者信息&#xff1a; Anna Agust-Torra, Marc Ferr-Mancebo, David Rincn-Rivera, Cristina Cervell Pastor, Sebasti Sallent-Ribes&#xff0c;来自西班牙巴塞罗那的加泰罗尼亚理工大学&…

裁剪视频如何让画质不变?一文教会你

当我们想要从一段视频中提取精华&#xff0c;裁剪视频就成了必不可少的技能。 但是&#xff0c;如何做到在裁剪过程中不损害画质&#xff0c;保持视频原有的清晰度和流畅度呢&#xff1f; 这不仅需要技巧&#xff0c;还需要对视频编辑有一定的了解。 本文将为你介绍四种裁剪…

Redis篇(数据类型)

目录 讲解一&#xff1a;简介 讲解二&#xff1a;常用 一、String类型 1. 简介 2. 常见命令 3. Key结构 4. 操作String 5. 实例 二、Hash类型 1. 简介 2. 常见命令 3. 3操作hash 4. 实例 三、List类型 1. 简介 2. 特征 3. 应用场景 4. 常见命令 5. 操作list …

13.安卓逆向-frida基础-编写hook脚本1

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信。 工…

外国电影演员识别系统源码分享

外国电影演员识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…

AI产品经理必知:核心人工智能技术概览

第一章&#xff1a;AI产品经理是否需要懂技术及其程度 在当今AI行业快速发展的背景下&#xff0c;作为一位AI产品经理&#xff0c;理解并掌握一定的AI技术知识不仅是锦上添花&#xff0c;更是不可或缺的素质。那么&#xff0c;AI产品经理究竟需要懂到何种程度的技术呢&#xf…

国内车市销量激增,理想成功超越BBA

文/王俣祺 导语&#xff1a;随着“金九银十”的到来&#xff0c;国内汽车市场迎来了一个充满活力的开局。乘用车市场的销量已经迎来新的突破&#xff0c;彰显出中国汽车市场的韧性和潜力。尤为引人注目的是&#xff0c;新能源汽车销量同样激增&#xff0c;成为推动市场增长的重…

C++--IO流

目录 1. C语言的输入与输出 2. 流是什么 3. CIO流 4 stringstream的简单介绍 1. C语言的输入与输出 C 语言中我们用到的最频繁的输入输出方式就是 scanf () 与 printf() 。 scanf(): 从标准输入设备 ( 键 盘 ) 读取数据&#xff0c;并将值存放在变量中 。 printf(): 将…

C++ : 多态

1. 多态的概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会 产生出不同的状态。 举个栗子&#xff1a;比如买票这个行为&#xff0c;当普通人买票时&#xff0c;是全价买票&#xff1b;学…

通过队列实现栈

请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 实现 MyStack 类&#xff1a; void push(int x) 将元素 x 压入栈顶。int pop() 移除并返回栈顶元素。int to…

基于微信小程序爱心领养小程序设计与实现(源码+定制+开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…