linux:线程同步

news2024/9/24 13:22:58

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》《Linux》

文章目录

  • 前言
  • 线程同步
    • 条件变量接口
    • 简单示例
      • pthread_cond_wait为什么要有mutex
      • 伪唤醒问题的解决 (if->while)
  • 总结


前言

本文作为我对于线程同步知识总结


线程同步

  • 同步:在保证数据安全的前提下,让线程能够按照某种顺序访问临界资源,从而有效避免饥饿问题,叫做同步
  • 竞态条件:是指多个线程同时访问系统共享资源时,由于其执行顺序或时间上的不确定性,导致数据不一致或其它不可预料的结果(一般发生在对称多处理环境,中断和异常处理,内核态抢占,并发执行…)

看了上面两个概念,你可能还是不太理解同步是什么,为什么要有同步。下面我们就举一个例子。
我们假定有两个人,一个人A将苹果放在桌子上,另一个人B蒙着眼睛去桌子上拿苹果,桌子每次只允许有一个人,因为B不清楚桌子上的情况是什么(有一个苹果,没有苹果,桌子上全是苹果),那为了保险起见,B只能疯狂的去桌子上拿苹果,以保证B拿到所有的苹果。那如果桌子上没有苹果,B仍然疯狂去桌子上拿苹果,此时A是不是就不能去桌子上放苹果,那此时B是不是再做无用工,而且导致了A不能放苹果,B拿苹果的效率降低。如果我们将A,B换成线程,苹果看出共享资源(某种任务),桌子看成临界区,那B是不是就是一直在做申请锁,再释放锁的无效工作,并且导致了A的饥饿问题。这时我们就需要保证A,B之间的顺序问题,如在B访问过桌子后,不能立即再次访问桌子,要等待A访问桌子后。这是不是就会使A,B拿苹果的效率提升。而这就是为什么要有同步的理由

条件变量接口

初始化条件变量

  • 静态初始化在这里插入图片描述
    与互斥锁类似,定义一个全局的条件变量,用PTHREAD_COND_INITIALIZER宏来初始化,系统自动释放该条件变量。该宏一般存放在 /usr/include/pthread.h 路径下
    在这里插入图片描述
  • 动态初始化
    在这里插入图片描述
    restrict是C语言的一个关键字,表示该指针是唯一的访问其指向对象的指针。
    cond将被初始化的条件变量,attr 为nullptr,使用默认属性初始化条件变量。
    如果函数成功执行,返回0; 如果函数执行失败,则返回错误码,如EAGAIN(资源暂时不可用),ENOMEM(内存不足)

销毁条件变量
在这里插入图片描述
cond 表示将被销毁的条件变量,需要注意在调用pthread_cond_destroy后,该指针本身并未被销毁,只是所指向的条件变量被销毁,记得将指针置空
如果函数成功执行,返回0; 如果函数失败,返回错误码。如EBUSY,该条件变量正在被使用


等待条件变量
在这里插入图片描述
cond 表示将要等待的条件变量,mutex 表示线程所持有的互斥锁
如果函数成功执行,返回0;如果函数执行失败,返回错误码,如EINVAL 无效的参数(条件变量 or 互斥锁为初始化…)

关于该函数的参数为什么会有锁,有什么注意事项,在下面代码示例,我们再解释。


唤醒等待

在这里插入图片描述
cond要发生信号的条件变量指针。
如果函数执行成功,返回0; 如果函数执行失败,返回错误码,如ENVAL(无效的参数)
需要注意的是,pthread_cond_signal只用于唤醒在cond条件变量的阻塞队列中等待的一个线程。

在这里插入图片描述
该函数的参数与返回值与pthread_cond_signal相同,只不过该函数唤醒所有在cond条件变量的阻塞队列中等待的所有线程,而那些线程先执行,取决于操作系统的调度策略。


简单示例

我们先来看看下面代码,3个线程争夺ticket资源。当三个线程检测到ticket == 0时,三个线程都将等待。我们主线程每过5秒,使ticket += 10。

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

using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ticket = 100;

void* threadRoutine(void *args)
{
    const string threadname = static_cast<char*>(args);
    
    while(true)
    {   
        usleep(1000);
        pthread_mutex_lock(&mutex);
        if(ticket > 0)
        {
            ticket--;
            cout << threadname << ", get a ticket: " << ticket << endl;
        }
        else
        {
            cout << threadname << ", ticket == 0" << endl;
            pthread_cond_wait(&cond, &mutex);
        }
        pthread_mutex_unlock(&mutex);
    }

    return nullptr;
}

int main()
{
    pthread_t td1;
    pthread_create(&td1, nullptr, threadRoutine, (void*)"thread-1");

    pthread_t td2;
    pthread_create(&td2, nullptr, threadRoutine, (void*)"thread-2");

    pthread_t td3;
    pthread_create(&td3, nullptr, threadRoutine, (void*)"thread-3");
    
    while(true)
    {
        sleep(5);
        pthread_mutex_lock(&mutex);
        ticket += 10;
        pthread_mutex_unlock(&mutex);

        pthread_cond_signal(&cond);
    }

    pthread_join(td1, nullptr);
    pthread_join(td2, nullptr);
    pthread_join(td3, nullptr);
    return 0;
}

在这里插入图片描述
当线程2,线程3,线程1先后检测到ticket == 0时,在cond的等待队列中,线程以2,3,1的顺序排队。那当调用pthread_cond_signal函数时,线程2会先执行,执行完再检测到ticket==0,排到等待队列尾部
在这里插入图片描述
再调用pthread_cond_signal函数时,线程3会执行。
在这里插入图片描述
再调用pthread_cond_signal函数时,线程1会执行。
在这里插入图片描述
这样,我们多线程就可以按某种特定顺序来执行。
那如果我们使用pthread_cond_broadcast函数,会有什么情况?
在这里插入图片描述
我们会发现,三个线程都被唤醒,来争抢ticket资源,其先后顺序由操作系统决定(优先级,竞争锁的能力…)。


pthread_cond_wait为什么要有mutex

此时不知道你是否有一个疑问,我们假定线程-1先争抢到ticket,检测到ticket == 0时,该线程-1要在cond的等待队列中等待,然后其它两个线程在进入临界区,检测到ticket==0,在等待队列中排队。但是线程-1是持有锁进入等待队列的,那其它两个线程是如何进入临界区的?答案很明显,那就是线程-1在cond等待中,一定释放了其持有的锁,从而使其余两个线程可以持有锁进入临界区。这就是为什么pthread_cond_wait函数的参数要有互斥锁的存在。
那我们在深入想一想,调用pthread_cond_wait函数后,该线程是先释放锁,再进入等待队列中;还是先进入等待队列中,再释放锁?答案是都不是。释放锁和进入等待队列是同时进行的!!!这也表示pthread_cond_wait函数是原子的,在调用pthread_cond_wait时,涉及的互斥锁释放和进入等待队列的操作是作为一个不可分割的整体来执行。确保了线程在调用该函数时不会遇到竞态条件,即线程在释放锁和进入等待队列之间不会被其它线程打断。
以下是线程调用pthread_cond_wait的过程

  1. 线程必须已经持有锁:在调用pthread_cond_wait时,线程必须已经锁定了某个互斥锁,这是该函数的前提条件

  2. 自动释放互斥锁:当线程调用pthread_cond_wait时,它会自动释放它当前持有的互斥锁。这一步是为了允许其它线程有机会获取该互斥锁并修改共享资源,从而可能改变条件变量的状态

  3. 加入等待队列:释放互斥锁之后,线程会接着被添加到条件变量的等待队列中,并在此处等待

  4. 等待被唤醒:线程在等待队列中等待,直到其它线程调用pthread_cond_signal 或 pthread_cond_broadcast来唤醒它

  5. 重新获取互斥锁:当线程被唤醒时,线程会尝试重新获取之前释放的互斥锁。如果锁此时没有被其它线程持有,线程将成功获取锁并继续执行。如果锁仍然被其它线程持有,线程将阻塞(在申请锁的地方阻塞),直到能够获取锁时。

具体来说,pthread_cond_wait函数的内部实现保证了2,3步骤的原子性。


伪唤醒问题的解决 (if->while)

伪唤醒问题是指:在多线程环境中,当线程等待某个条件变量时,它可能会在没有到达预期的情况下被唤醒。
对于这一问题,我们可以在判断的时候,将if 变为 while,使其被唤醒后任然进行条件判断,如果条件满足,线程继续向后执行,如果条件不被满足,线程继续在该条件变量下等待。

pthread_mutex_lock(&mutex);
// 访问临界区 
while(条件为假)
	pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

总结

以上就是我对于线程同步的总结。

在这里插入图片描述

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

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

相关文章

Appium设备交互API

设备交互API指的是操作设备系统中的一些固有功能&#xff0c;而非被测程序的功能&#xff0c;例如模拟来电&#xff0c;模拟发送短信&#xff0c;设置网络&#xff0c;切换横竖屏&#xff0c;APP操作&#xff0c;打开通知栏&#xff0c;录屏等。 模拟来电 make_gsm_call(phon…

大数据之scala

为什么学习scala spark是新一代内存级大数据计算框架&#xff0c;是大数据的重要内容 spark就是使用scala编写的&#xff0c;因此为了更好的学习spark&#xff0c;需要掌握scala这门语言 spark的兴起&#xff0c;带动scala语言的发展 scala发展历史 联邦理工学院的马丁 奥德…

走迷宫(BFS两种写法)

题目描述&#xff1a; 给定一个 nm 的二维整数数组&#xff0c;用来表示一个迷宫&#xff0c;数组中只包含 0 或 1&#xff0c;其中 0 表示可以走的路&#xff0c;1 表示不可通过的墙壁。 最初&#xff0c;有一个人位于左上角 (1,1) 处&#xff0c;已知该人每次可以向上、下、…

市场复盘总结 20240328

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 40% 最常用的…

2024 ccfcsp认证打卡 2023 03 02 垦田计划

import java.util.*;public class Main {public static void main(String[] args) {Scanner input new Scanner(System.in);int N 100100; // 定义一个较大的常数Nlong[] t new long[N]; // 存储任务的耗时long[] c new long[N]; // 存储每块区域投入资源的数量long[] c…

SpringCloud和SpringCloudAlibaba的区别

1、SpringCloud和SpringCloudAlibaba的区别 SpringCloudAlibaba实际上对我们的SpringCloud2.x和1.x实现拓展组件功能。 nacos是分布式配置中心分布式注册中心Eurekaconfig。 研发SpringCloudAlibaba目的是为了推广阿里的产品&#xff0c;如果使用了SpringCloudAlibaba,最好使…

什么?想让视频号小店领先同行,竟然这么简单!

大家好&#xff0c;我是电商小布。 视频号小店从推出到现在&#xff0c;逐渐也是被越来越多的人所熟知了。 虽然说当前市场内部的商家数量并不多&#xff0c;竞争力不大。 但是在入驻之后想要领先同行商家&#xff0c;产生更好的店铺数据&#xff0c;该怎么来做呢&#xff1…

18.字面量

文章目录 一、字面量二、区分技巧三、扩展&#xff1a; /t 制表符 一、字面量 在有些资料&#xff0c;会把字面量说成常量、字面值常量&#xff0c;这种叫法都不是很正确&#xff0c;最正确的叫法还是叫做&#xff1a;字面量。 作用&#xff1a;告诉程序员&#xff0c;数据在…

【智能算法改进】混沌映射策略--一网打尽

目录 1.引言2.混沌映射3.分布特征4.混沌映射函数调用5.改进智能算法 1.引言 基本种群初始化是在整个空间内随机分布&#xff0c;具有较高的随机性和分布不均匀性&#xff0c;会导致种群多样性缺乏&#xff0c;搜索效率低等问题。 许多学者利用混沌映射机制来增加种群的多样性&…

国内外主要气象卫星介绍

NOAA AVHRR介绍 美国NOAA极轨卫星从1970年12月第一颗发射以来&#xff0c;近40年连续发射了18颗&#xff0c;最新的NOAA-19也将在2009年发射升空。NOAA卫星共经历了5代&#xff0c;目前使用较多的为第五代NOAA卫星&#xff0c;包括NOAA-15—NOAA-18&#xff1b;作为备用的第四…

17.注释和关键字

文章目录 一、 注释二、关键字class关键字 我们之前写的HelloWorld案例写的比较简单&#xff0c;但随着课程渐渐深入&#xff0c;当我们写一些比较难的代码时&#xff0c;在刚开始写完时&#xff0c;你知道这段代码是什么意思&#xff0c;但是等过了几天&#xff0c;再次看这段…

GEC6818开机自动加载驱动与更改开发板的RTC时钟

GEC6818开机自动加载驱动与更改开发板的RTC时钟 本文主要涉及&#xff1a; 1.GEC6818开机自动加载驱动 2.更改开发板的RTC时钟 文章目录 GEC6818开机自动加载驱动与更改开发板的RTC时钟一、开机自动加载驱动或运行程序**STEP1&#xff1a;** 使用vi打开文件profile.命令如下**S…

LinkedIn 互联网架构扩展简史

LinkedIn成立于 2003 年&#xff0c;其目标是连接到您的网络以获得更好的工作机会。第一周只有 2,700 名会员。时间快进了很多年&#xff0c;LinkedIn 的产品组合、会员基础和服务器负载都取得了巨大的增长。 如今&#xff0c;LinkedIn 在全球运营&#xff0c;拥有超过 3.5 亿会…

Portainer的替代Dockge?又一个Docker Compose管理器?

Dockge&#xff1a;让Docker Compose管理触手可及&#xff0c;一图胜千言&#xff0c;轻松构建与管控您的容器服务栈&#xff01;- 精选真开源&#xff0c;释放新价值。 概览 Docker&#xff0c;这一开放源代码的创新平台&#xff0c;旨在实现应用程序部署、扩展与运维的自动化…

术语技巧:如何格式化网页中的术语

术语是语言服务中的核心语言资产。快速处理英汉对照的术语是我们在翻译技术学习过程中需要掌握的必备技能。 通常&#xff0c;我们需要把在权威网站上收集到的术语放到word当中&#xff0c;调整正左右对齐的样式&#xff0c;便于打印学习或者转化为Excel表。 如何快速实现这一…

docker容器下部署hbase并在springboot中通过jdbc连接

我在windows的docker中部署了一个hbase服务&#xff0c;然后用springboot连接到此服务并访问数据。 详情可参考项目中的README.md。项目中提供了用于构建镜像的dockerfile&#xff0c;以及测试代码。 项目连接&#xff1a; https://gitee.com/forgot940629/hbase_phoenix_sprin…

可解释 AI 系统及其构建方式的实用指南——可解释AI实战(PyTorch版)

过去五年中&#xff0c;我们就见证了人工智能(Artifcial Intelligence&#xff0c;AI)领域的重大突破&#xff0c;特别是在图像识别、自然语言理解等领域&#xff0c;以及围棋等棋盘游戏领域。随着人工智能在医疗和金融等行业的广泛应用&#xff0c;它正在辅助人类做出关键的决…

蚂蚁庄园今日答案

蚂蚁庄园是一款爱心公益游戏&#xff0c;用户可以通过喂养小鸡&#xff0c;产生鸡蛋&#xff0c;并通过捐赠鸡蛋参与公益项目。用户每日完成答题就可以领取鸡饲料&#xff0c;使用鸡饲料喂鸡之后&#xff0c;会可以获得鸡蛋&#xff0c;可以通过鸡蛋来进行爱心捐赠。其中&#…

Java中有哪些容器(集合类)?

Java中的集合类主要由Collection和Map这两个接口派生而出&#xff0c;其中Collection接口又派生出三个子接 口&#xff0c;分别是Set、List、Queue。所有的Java集合类&#xff0c;都是Set、List、Queue、Map这四个接口的实现 类&#xff0c;这四个接口将集合分成了四大类&#…

iOS - Runtime-API

文章目录 iOS - Runtime-API1. Runtime应用1.1 字典转模型1.2 替换方法实现1.3 利用关联对象给分类添加属性1.4 利用消息转发机制&#xff0c;解决方法找不到的异常问题 2. Runtime-API2.1 Runtime API01 – 类2.1.1 动态创建一个类&#xff08;参数&#xff1a;父类&#xff0…