当前位置: 首页 > article >正文

Linux线程同步:深度解析条件变量接口

🍑个人主页:Jupiter.
🚀 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

  • `🍑Linux线程同步`
    • `🐉条件变量---实现线程同步`
      • `💧同步概念与竞态条件`
      • `🐆条件变量接口`
            • *初始化函数*
            • *销毁条件变量*
            • *等待条件满足*
            • *唤醒等待函数*
      • *🦅为什么 pthread_cond_wait 需要互斥锁?*
          • *条件变量使用规范*


🍑Linux线程同步

🐉条件变量---实现线程同步

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,直到到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

💧同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解。

🐆条件变量接口

初始化函数

可以使用预定义的宏PTHREAD_COND_INITIALIZER来静态初始化全局的pthread_cond_t变量

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

  • 参数:
    • cond:要初始化的条件变量
    • attr:NULL
销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

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

  • 参数:
    • cond:要在这个条件变量上等待
    • mutex:互斥锁,调用 pthread_cond_wait 函数后,会自动释放持有的锁,待被唤醒后,函数调用成功返回了,会自动加锁
唤醒等待函数
  • 将指定条件变量下等待的全部线程唤醒:
    int pthread_cond_broadcast(pthread_cond_t *cond);

  • 将指定条件变量下等待的一个线程唤醒:
    int pthread_cond_signal(pthread_cond_t *cond);

🦅为什么 pthread_cond_wait 需要互斥锁?

1. 保护共享资源
在多线程环境中,多个线程可能会同时访问和修改共享资源。这些共享资源通常被互斥锁保护,以确保在任何时候只有一个线程可以访问它们。当线程调用pthread_cond_wait时,它通常位于访问这些共享资源的临界区内。因此,需要互斥锁来确保在调用pthread_cond_wait之前和之后,共享资源的状态不会被其他线程意外修改。

2. 防止虚假唤醒
pthread_cond_wait函数可能会因为多种原因被唤醒,包括真实的条件变化(由pthread_cond_signal或pthread_cond_broadcast触发)或其他系统事件(如信号或中断)。这种非条件触发的唤醒被称为“虚假唤醒”。为了避免虚假唤醒导致的错误,线程在pthread_cond_wait返回后需要再次检查条件是否真正满足。这个检查过程需要互斥锁的保护,以防止在检查条件时共享资源被其他线程修改。

3. 原子性
在调用pthread_cond_wait之前,线程需要释放互斥锁,以便其他线程可以修改条件并发出信号。然而,仅仅释放锁并调用pthread_cond_wait并不是原子的,这意味着在释放锁和进入等待状态之间可能存在时间窗口,其他线程可能会修改条件。为了解决这个问题,pthread_cond_wait函数内部会先释放锁,然后等待条件变量的信号。当条件满足且线程被唤醒时,pthread_cond_wait会重新获取之前释放的互斥锁,以确保操作的原子性

4. 防止死锁
如果在调用pthread_cond_wait时没有持有互斥锁,或者在调用后没有重新获取互斥锁,都可能导致死锁或其他同步问题。例如,如果线程在检查条件后没有立即释放锁就调用pthread_cond_wait,那么它可能会因为已经持有锁而无法被pthread_cond_signal或pthread_cond_broadcast唤醒。同样,如果线程在pthread_cond_wait返回后没有重新获取锁,那么它可能会访问一个处于不一致状态的共享资源。


按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了,如下代码:

// 错误的设计 
 pthread_mutex_lock(&mutex); 
 while (condition_is_false) { 
 pthread_mutex_unlock(&mutex); 
 //解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过 
 pthread_cond_wait(&cond); 
 pthread_mutex_lock(&mutex); 
 } 
 pthread_mutex_unlock(&mutex); 
  • 由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥锁,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。
  • int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥锁变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样。
条件变量使用规范
  • 等待条件代码
pthread_mutex_lock(&mutex); 
 while (条件为假) 
		 pthread_cond_wait(cond, mutex); 
 //修改条件 
 pthread_mutex_unlock(&mutex);
  • 给条件发送信号代码
pthread_mutex_lock(&mutex); 
//设置条件为真 
pthread_cond_signal(cond); 
pthread_mutex_unlock(&mutex);

示例代码:

#include <unistd.h>
#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <cstdlib>

using namespace std;

pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;

void *Mastercore(void *args)
{
    sleep(3);
    cout << "Master start runing ..." << endl;
    string name = static_cast<const char *>(args);

    while (true)
    {
        pthread_cond_broadcast(&gcond); //唤醒指定变量条件下等待的所有线程
        // pthread_cond_signal(&gcond); // 唤醒指定变量条件下等待的队首的线程
        cout << " Master 唤醒了一个线程 ... " << endl;
        sleep(1);
    }

    return nullptr;
}

void *SlaverCore(void *args)
{
    string name = static_cast<const char *>(args);

    while (true)
    {
        // 加锁
        pthread_mutex_lock(&gmutex);
        // 设定条件变量--这时是持有锁的,需要将锁传入,调用wait的时候,会自动解锁
        pthread_cond_wait(&gcond, &gmutex);
        cout << "wait is : " << name << endl;
        // 解锁
        pthread_mutex_unlock(&gmutex);
    }
    return nullptr;
}

void StartMaster(vector<pthread_t> *threadsptr)
{
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, Mastercore, (void *)"Master thread");
    if (n == 0)
    {
        cout << "Master create sucess..." << endl;
    }
    threadsptr->emplace_back(tid);
}

void StartSlaver(vector<pthread_t> *threadsptr, int n = 3)
{
    for (int i = 0; i < n; i++)
    {
        pthread_t tid;
        char *name = new char[64];   //注意需要new
        snprintf(name, 64, "Slaver-%d", i + 1);
        int n = pthread_create(&tid, nullptr, SlaverCore, (void *)name);
        if (0 == n)
        {
            cout << "create " << name << " success..." << endl;
        }

        threadsptr->emplace_back(tid);
    }
}

void WaitAll(vector<pthread_t> &threads)
{
    for (auto &t : threads)
    {
        pthread_join(t, nullptr);
    }
}

int main()
{
    vector<pthread_t> threads;
    StartMaster(&threads);
    StartSlaver(&threads, 5);

    WaitAll(threads);

    return 0;
}


http://www.kler.cn/news/306895.html

相关文章:

  • Deep Learning-Based Object Pose Estimation:A Comprehensive Survey
  • VUE使用echarts编写甘特图(组件)
  • AI写作助力自媒体,传统模式将被颠覆
  • 网络安全学习(二)初识kali
  • SAP EWM Cross Docking (CD) 越库操作
  • 探索Python中的装饰器
  • 前端基础知识+算法(一)
  • 8- 【JavaWeb】用HTML和CSS来创建一个简洁的登录界面
  • OpenCV_图像像素读写操作
  • STM32_startup文件详解
  • 性能测试的复习4-数据库连接、控制器、定时器
  • 人脸防伪检测系统源码分享
  • 多线程下的共享变量访问数据竞争的问题
  • SSM框架学习
  • GD32E230 RTC报警中断功能使用
  • DockerDocker Compose安装(离线+在线)
  • 汽车免拆诊断案例 | 沃尔沃V40 1.9TD断续工作
  • ensp—相关ospf-3
  • SpringBoot 消息队列RabbitMQ 交换机模式 Fanout广播 Direct定向 Topic话题
  • react使用技巧
  • Spring6学习笔记4:事务
  • Spring Boot-消息队列相关问题
  • C++从入门到起飞之——继承下篇(万字详解) 全方位剖析!
  • Pr:首选项 - 媒体
  • python打通hive数据库实现CRUD
  • 力扣最热一百题——螺旋矩阵
  • 动态住宅代理网络在广告验证中的作用
  • 卡车配置一键启动无钥匙进入手机控车
  • 详细分析Uniapp中的轮播图基本知识(附Demo)
  • PHP7 json_encode() 浮点小数溢出错误