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

Linux -- 同步与条件变量

目录

同步

条件变量

pthread_cond_t

pthread_cond_init(初始化条件变量)

pthread_cond_destroy(销毁条件变量)

pthread_cond_wait(线程等待条件变量)

重要提醒 

pthread_cond_boardcast(唤醒等待队列中的所有线程)

pthread_cond_singal(唤醒一个正在等待的线程)

代码

条件变量的使用

唤醒一个线程:

广播唤醒线程:


同步

在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,确保这些执行单元以一种有序、非冲突的方式进行交互,从而有效避免饥饿问题,叫做同步

条件变量

条件变量的主要用途是用于实现同步,在一个或多个线程等待某个特定条件变为真时,可以被阻塞(挂起),直到其他线程通知这个条件已经满足。它提供了一种机制来安全地等待某个条件,并在条件满足时唤醒等待的线程。条件变量通常与互斥锁pthread_mutex_t)一起使用,以保护共享资源和确保线程安全。

条件变量其实是一种通知机制 + 等待队列

  • 当某种特定的条件未成立时,线程在等待队列中排队
  • 当条件成立时,可以选择唤醒队头的线程或者队列中的所有线程,继续执行线程任务。

pthread_cond_t

线程库中用于条件变量的一种数据类型

pthread_cond_init(初始化条件变量)

#include <pthread.h>

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

cond:指向要初始化的条件变量的指针。

attr:指向条件变量属性对象的指针。如果只需要默认属性,可以传递 NULL

 返回值:

  • 成功时,返回 0
  • 失败时,返回错误编号。

pthread_cond_destroy(销毁条件变量)

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);

cond:指向要销毁的条件变量的指针。

返回值:

  • 成功时,返回 0
  • 失败时,返回错误编号。

pthread_cond_wait(线程等待条件变量)

#include <pthread.h>

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

cond:指向要等待的条件变量的指针。

mutex:指向已经加锁的互斥锁的指针。该互斥锁应该保护着与条件相关的共享数据。

返回值:

  • 成功时,返回 0
  • 失败时,返回错误编号。

重要提醒 

  • 在调用 pthread_cond_wait 时,当前线程必须持有由 mutex 指向的互斥锁。
  • 调用此函数后,pthread_cond_wait 会自动释放互斥锁,并将当前线程置为等待状态直到某个其他线程调用 pthread_cond_signal 或 pthread_cond_broadcast 来唤醒它。
  • 当前线程被唤醒后,pthread_cond_wait 会重新尝试获取互斥锁;只有成功获取到互斥锁之后,函数才会返回。
  • 返回时,互斥锁再次被当前线程持有

pthread_cond_boardcast(唤醒等待队列中的所有线程)

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);

cond:指向要广播信号的条件变量的指针。

返回值:

  • 成功时,返回 0
  • 失败时,返回错误编号。
  • 当调用 pthread_cond_broadcast 时,所有当前正在等待该条件变量的线程都会被唤醒。
  • 每个被唤醒的线程在继续执行之前会重新尝试获取与条件变量关联的互斥锁。
  • 注意,这并不意味着所有被唤醒的线程都会立即运行;它们仍然需要按照操作系统的调度算法来获得CPU时间。

pthread_cond_singal(唤醒一个正在等待的线程)

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

cond:指向要发出信号的条件变量的指针。

返回值:

  • 成功时,返回 0
  • 失败时,返回错误编号。
  • 当调用 pthread_cond_signal 时,如果有线程正在等待该条件变量,则会唤醒其中一个线程。
  • 被唤醒的线程在继续执行之前会重新尝试获取与条件变量关联的互斥锁。
  • 如果没有线程在等待该条件变量,那么调用 pthread_cond_signal 将不会有任何效果。 

代码

条件变量的使用

条件变量通常与互斥锁配套使用,线程持有互斥锁了,才可以去检查某个特定的条件是否满足,满足了才能去访问临界资源,如果不满足就去排队,所以条件变量一般在加锁和解锁之间使用,所以在排队的线程曾经拥有互斥锁

线程去排队时,需要把曾经申请到的互斥锁释放掉,让其他线程去申请锁,线程不可以拿着锁去排队,否则无法达到同步,无法保持良好的并发性!所以 pthread_cond_wait 函数的参数还包括指向已经加锁的互斥锁的指针,就是为了把线程曾经申请到的互斥锁释放掉!

唤醒一个线程:

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

pthread_cond_t gcond=PTHREAD_COND_INITIALIZER;//条件变量
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;//互斥锁

void* SlaverCore(void* args)
{
    std::string name=static_cast<const char*>(args);
    //被唤醒
    while(true)
    {
        pthread_mutex_lock(&gmutex);//加锁

        //一般条件变量在加锁与解锁之间使用
        pthread_cond_wait(&gcond,&gmutex);

        //等待成功,被唤醒了
        std::cout<<"当前被唤醒的线程为:"<<name<<std::endl;

        pthread_mutex_unlock(&gmutex);//解锁

    }
}
void* MasterCore(void* args)
{
    sleep(3);
    std::cout<<"master 开始工作..."<<std::endl;
    //唤醒
    while(true)
    {
        pthread_cond_signal(&gcond);

        std::cout<<"master 唤醒线程..."<<std::endl;

        sleep(1);
    }
}

//创建一批被控制的线程
void StartSlaver(std::vector<pthread_t> *tidsptr,int num=3)
{
    for(int i=0;i<num;i++)
    {
        pthread_t tid;
        char *name=new char[64];
        snprintf(name,64,"slaver-%d",i+1);
        int n=pthread_create(&tid,nullptr,SlaverCore,name);
        if(n==0)
        {
            std::cout<<"create success: "<<name<<std::endl;
            tidsptr->emplace_back(tid);
        }
    }
}

//创建主线程
void StartMaster(std::vector<pthread_t> *tidsptr)
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,MasterCore,nullptr);
    if(n==0)//创建成功
    {
        std::cout<<"create master success"<<std::endl;
    }
    tidsptr->emplace_back(tid);
}

void WaitThread(std::vector<pthread_t> tids)
{
    for(auto &tid:tids)
    {
        pthread_join(tid,nullptr);//等待线程
    }
}
int main()
{
    std::vector<pthread_t> tids;//存线程id
    StartMaster(&tids);
    StartSlaver(&tids,5);
    WaitThread(tids);
    return 0;
}

广播唤醒线程:

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

pthread_cond_t gcond=PTHREAD_COND_INITIALIZER;//条件变量
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;//互斥锁

void* SlaverCore(void* args)
{
    std::string name=static_cast<const char*>(args);
    //被唤醒
    while(true)
    {
        pthread_mutex_lock(&gmutex);//加锁

        //一般条件变量在加锁与解锁之间使用
        pthread_cond_wait(&gcond,&gmutex);

        //等待成功,被唤醒了
        std::cout<<"当前被唤醒的线程为:"<<name<<std::endl;

        pthread_mutex_unlock(&gmutex);//解锁

    }
}
void* MasterCore(void* args)
{
    sleep(3);
    std::cout<<"master 开始工作..."<<std::endl;
    //唤醒
    while(true)
    {
        pthread_cond_broadcast(&gcond);
        
        std::cout<<"master 唤醒线程..."<<std::endl;

        sleep(1);
    }
}

//创建一批被控制的线程
void StartSlaver(std::vector<pthread_t> *tidsptr,int num=3)
{
    for(int i=0;i<num;i++)
    {
        pthread_t tid;
        char *name=new char[64];
        snprintf(name,64,"slaver-%d",i+1);
        int n=pthread_create(&tid,nullptr,SlaverCore,name);
        if(n==0)
        {
            std::cout<<"create success: "<<name<<std::endl;
            tidsptr->emplace_back(tid);
        }
    }
}

//创建主线程
void StartMaster(std::vector<pthread_t> *tidsptr)
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,MasterCore,nullptr);
    if(n==0)//创建成功
    {
        std::cout<<"create master success"<<std::endl;
    }
    tidsptr->emplace_back(tid);
}

void WaitThread(std::vector<pthread_t> tids)
{
    for(auto &tid:tids)
    {
        pthread_join(tid,nullptr);//等待线程
    }
}
int main()
{
    std::vector<pthread_t> tids;//存线程id
    StartMaster(&tids);
    StartSlaver(&tids,5);
    WaitThread(tids);
    return 0;
}


http://www.kler.cn/a/452113.html

相关文章:

  • Redis-十大数据类型
  • .NET常用的ORM框架及性能优劣分析总结
  • Node.js 工具:在 Windows 11 中配置 Node.js 的详细步骤
  • QT-基础-1-Qt 中的字符串处理与常见数据类型
  • SpringBoot3-第六篇(整合NoSQL)
  • 网站服务器被攻击了怎么办?
  • 在线excel编辑(luckysheet)
  • 一网多平面
  • WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
  • clickhouse查询使用order by和limit,不同limit查询出现重复数据问题【已解决】
  • 3GPP R18 MT-SDT
  • 字符编码(三)
  • 2.系统学习-逻辑回归
  • 怎么在ubuntu系统上安装qt项目的打包工具linuxdeployqt
  • 目标检测与R-CNN——paddle部分
  • 前端面经每日一题Day21
  • MDS-NPV/NPIV
  • 如何完全剔除对Eureka的依赖,报错Cannot execute request on any known server
  • pytorch nn.Unflatten 和 nn.Flatten模块介绍
  • Chrome 浏览器插件获取网页 iframe 中的 window 对象
  • 【ORB-SLAM3:相机针孔模型和相机K8模型】
  • Chapter 03 复合数据类型-1
  • RBF分类-径向基函数神经网络(Radial Basis Function Neural Network)
  • 数据库安全-redisCouchdb
  • 硬件设计-传输线匹配
  • 3D视觉坐标变换(像素坐标转换得到基于相机坐标系的坐标)