【Linux】线程同步

这篇具有很好参考价值的文章主要介绍了【Linux】线程同步。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

条件变量相关函数

初始化条件变量-pthread_cond_init

初始化条件变量的函数叫做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_init函数初始化条件变量叫做动态分配,我们还可以用静态分配的方式初始化条件变量

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

销毁条件变量-pthread_cond_destroy

销毁条件变量的函数叫做pthread_cond_destroy

#include<pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);

参数说明

cond:需要销毁的条件变量

返回值说明

销毁成功返回0,失败返回错误码


注意:使用PTHREAD_COND_INITIALIZER初始化 (静态分配)的条件变量不需要销毁


等待条件变量-pthread_cond_wait

等待条件变量满足的函数叫做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_broadcast
#include<pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal
#include<pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);

参数说明

cond:唤醒在cond条件变量下等待的线程

返回值说明

函数调用成功返回0,失败返回错误码


区别

  • pthread_cond_signal函数用于唤醒等待队列中首个线程
  • pthread_cond_broadcast函数用于唤醒等待队列中的全部线程

小例子

下面我们用主线程创建三个新线程,让主线程控制这三个新线程, 这三个新线程创建后都在条件变量下进行等待,直到被主线程唤醒

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

pthread_mutex_t mtx;//互斥锁
pthread_cond_t cond;//条件变量

//主线程控制新线程
void *ctrl(void *args)
{
    std::string name = (char*)args;
    while(1)
    {
        std::cout << "master say : begin work" << std::endl;
        //pthread_cond_signal函数作用:唤醒在条件变量下等待的 一个 线程
        //哪一个呢?当前在等待队列里等待的第一个线程
        pthread_cond_signal(&cond); 
        sleep(2);
    }
}
void *work(void *args)
{
    int number = *(int*)args;
    delete (int*)args;
    while(1)
    {
        pthread_cond_wait(&cond, &mtx);//等待条件变量
        std::cout << "worker: " << number << " is working ..." << std::endl;
    }
}
#define NUM 3
int main()
{
    pthread_mutex_init(&mtx, nullptr);//初始化这把锁
    pthread_cond_init(&cond, nullptr);//初始化条件变量
    pthread_t master;
    pthread_t worker[NUM];
    pthread_create(&master, nullptr, ctrl, (void*)"boss");
    //创建NUM个线程
    for(int i = 0; i < NUM; i++)
    {
        int *number = new int(i);
        pthread_create(worker+i, nullptr, work, (void*)number);
    }
    //线程等待
    for(int i = 0; i < NUM; i++)
    {
        pthread_join(worker[i], nullptr);
    }

    pthread_join(master, nullptr);//线程等待
    
    pthread_mutex_destroy(&mtx);//释放锁
    pthread_cond_destroy(&cond);//释放条件变量
    return 0;
}

此时我们会发现唤醒这三个线程时具有明显的顺序性

根本原因是当这若干个线程启动时默认都会在该条件变量下去等待,而我们每次都唤醒的是在当前条件变量下等待的头部线程,当该线程执行完打印操作后会继续排到等待队列的尾部进行wait,所以我们能够看到一个周转的现象

【Linux】线程同步


如果我们想每次唤醒都将在该条件变量下等待的所有线程进行唤醒,可以将代码中的pthread_cond_signal函数改为pthread_cond_broadcast函数

//主线程控制新线程
void *ctrl(void *args)
{
    std::string name = (char*)args;
    while(1)
    {
        std::cout << "master say : begin work" << std::endl;
        pthread_cond_broadcast(&cond);
        sleep(2);
    }
}

此时我们每一次唤醒都会将所有在该条件变量下等待的线程进行唤醒==>也就是每次都将这三个线程唤醒

【Linux】线程同步


关于等待函数的补充

为什么pthread_cond_wait函数的第二个参数需要传入互斥量

1)条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程

2)条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化,所以一定要用互斥锁来保护,没有互斥锁就无法安全的获取和修改共享数据

【Linux】线程同步

3)当线程进入临界区时需要先加锁,然后判断内部资源的情况,若不满足当前线程的执行条件,则需要在该条件变量下进行等待,但此时该线程是拿着锁被挂起的,也就意味着这个锁再也不会被释放了,此时就会发生死锁问题

4)所以在调用pthread_cond_wait函数时,还需要将对应的互斥锁传入,此时当线程因为某些条件不满足需要在该条件变量下进行等待时,就会自动释放该互斥锁

5)当该线程被唤醒时,该线程会接着执行临界区内的代码,此时便要求该线程必须立马获得对应的互斥锁,因此当某一个线程被唤醒时,实际会自动获得对应的互斥锁


总结:

  • 当该线程进入等待的时候,互斥锁会自动释放,而当该线程被唤醒时,又会自动获得对应的互斥锁
  • 条件变量需要配合互斥锁使用,其中条件变量是用来完成同步的,而互斥锁是用来完成互斥的
  • 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函数后,会先判断条件变量是否等于0,若等于0则说明不满足,此时会先将对应的互斥锁解锁,直到pthread_cond_wait函数返回时再将条件变量改为1,并将对应的互斥锁加锁


条件变量使用规范

等待条件变量的代码

pthread_mutex_lock(&mutex);//加锁
while (条件为假)
	pthread_cond_wait(&cond, &mutex);//条件不满足,就一直等待
修改条件
pthread_mutex_unlock(&mutex);//解锁

唤醒等待线程的代码文章来源地址https://www.toymoban.com/news/detail-464780.html

pthread_mutex_lock(&mutex);//加锁
设置条件为真
pthread_cond_signal(&cond);//满足条件了,唤醒在条件变量等待队列当中的第一个线程
pthread_mutex_unlock(&mutex);//解锁

到了这里,关于【Linux】线程同步的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【Linux系统编程:线程】 线程控制 -- 创建、终止、等待、分离 | 线程互斥与同步 | 互斥量与条件变量 | 生产者消费者模型 | 线程池 | STL/智能指针与线程安全 | 读者写者模型

    【Linux系统编程:线程】 线程控制 -- 创建、终止、等待、分离 | 线程互斥与同步 | 互斥量与条件变量 | 生产者消费者模型 | 线程池 | STL/智能指针与线程安全 | 读者写者模型

    写在前面 本文重点: 了解线程概念,理解线程与进程区别与联系。 学会线程控制,线程创建,线程终止,线程等待。 了解线程分离与线程安全。 学会线程同步。 学会使用互斥量,条件变量,posix 信号量,以及读写锁。 理解基于读写锁的读者写者问题。 一、线程概念 💦

    2024年02月04日
    浏览(38)
  • 4.【CPP】入门(初始化列表||explicit||static||友元||静态成员变量/函数)

    4.【CPP】入门(初始化列表||explicit||static||友元||静态成员变量/函数)

    我们知道在c++11中才能在成员对象声明时初始化,像下面这样。 注意:构造函数不是初始化,而是赋初始值。那么在c++11以前该怎么初始化成员变量呢? 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次) 类中包含以下成员,必须放在初始化列表位置进行初始

    2024年01月20日
    浏览(16)
  • Linux--线程-条件控制实现线程的同步

    Linux--线程-条件控制实现线程的同步

    1.条件变量 条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。 条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之

    2024年02月05日
    浏览(9)
  • 七、初始化环境变量

    void env_relocate (void) { if (gd-env_valid == 0) { puts (\\\"*** Warning - bad CRC, using default environmentnn\\\"); show_boot_progress (-60); set_default_env(); } else { env_relocate_spec (); } gd-env_addr = (ulong)(env_ptr-data); } void env_relocate_spec (void) { #if !defined(ENV_IS_EMBEDDED) int ret; ret = readenv(CONFIG_ENV_OFFSET, (u_char *) env_ptr); if

    2023年04月09日
    浏览(14)
  • kotlin 初始化变量

    2024年02月16日
    浏览(7)
  • golang变量初始化顺序

    顺序: 1.引用的包 2.全局变量 3.init()函数 4.main()函数 输出 $ go run 1.go pkg init func() main init main()

    2024年04月17日
    浏览(17)
  • pthread 变量静态初始化 避免使用被销毁过的变量

    互斥锁: pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; 读写锁: pthread_rwlock_t g_rwlock = PTHREAD_RWLOCK_INITIALIZER; 条件变量: pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER; 以互斥锁为例,当持有互斥锁的线程,需要进行互斥锁的销毁工作时,无法确保当前没有任何其他线程会在锁销毁之后,仍

    2024年02月06日
    浏览(9)
  • 【JavaScript】JavaScript 变量 ① ( JavaScript 变量概念 | 变量声明 | 变量类型 | 变量初始化 | ES6 简介 )

    【JavaScript】JavaScript 变量 ① ( JavaScript 变量概念 | 变量声明 | 变量类型 | 变量初始化 | ES6 简介 )

    JavaScript 变量 是用于 存储数据 的 容器 , 通过 变量名称 , 可以 获取 / 修改 变量 中的数据 ; 变量 的 本质 是 存放数据 的 一块内存空间 ; 在 JavaScript 中, 使用 var / let / const 来声明变量 , 每个变量都有一个 变量名 和 一个 变量值 ; JavaScript 变量声明 : var : 使用

    2024年03月15日
    浏览(10)
  • go语言包、变量、init初始化顺序

    go语言包、变量、init初始化顺序

    一个完整的 go 语言可运行程序,通常会包含引用的包、变量、init 函数以及 main 函数几个部分。 包、变量、常量、init 函数以及 main 函数初始化顺序如下图所示: 在一个 go 语言程序中,初始化顺序规则如下: 引入的包 当前包中的变量、常量 当前包的 init 函数 main 函数 初始

    2023年04月14日
    浏览(12)
  • 【Golang入门教程】Go语言变量的初始化

    【Golang入门教程】Go语言变量的初始化

    强烈推荐 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站: 人工智能 推荐一个个人工作,日常中比较常用的人工智能工具,无需魔法,忍不住分享一下给大家。点击跳转到网站: 人工智能工具 引言 在Go语言中,变量

    2024年04月17日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包