【Linux】多线程编程 - 同步/条件变量/信号量

news2024/11/16 17:57:15

目录

一.线程同步

1.什么是线程同步

2.为什么需要线程同步

3.如何实现线程同步

二.条件变量

1.常见接口以及使用

2.wiat/signal中的第二个参数mutex的意义

3.代码验证

三.POSIX信号量

1.概念

2.常见接口以及使用

四.条件变量vsPOSIX信号量


一.线程同步

1.什么是线程同步

同步: 在保证数据安全的情况下, 让线程能按照某种特定顺序访问临界资源, 从而有效避免饥饿问题, 主要为了解决访问临界资源合理性问题

其中, 按照某种特定顺序访问临界资源, 实际上操作系统的调度队列自动帮助我们维护的, 对于我们而言是透明的, 因为如果有线程在等待资源, 那么一定是一个个的被挂起, 自然就有了顺序性

2.为什么需要线程同步

情景一: 假设有某个线程竞争能力比较强, 就会导致其他线程迟迟访问不到临界资源, 造成饥饿问题

情景二: 线程在使用临界资源前必须要检测临界资源是否就绪, 检测这一过程本质上也是访问临界资源, 那么就需要互斥的访问(加锁解锁), 如果临界资源没有准备就绪, 线程就会不断地检测, 即不断地互斥访问, 也就是在不断加锁解锁, 频繁做无效的加锁解锁工作, 是一种极大的资源浪费

情景三: 生活中的例子, 当我们要去商店买东西时, 总是要问一下是否还有货, 如果没有就要改天再来, 那么我们不可能每次问的时候都在亲自跑到商店去问, 而是在第一次问的时候就留好了联系方式, 如果有货的话老板会联系我来拿, 这本质上就是资源已就绪, 老板唤醒了我这个等待中的线程, 让我来访问

3.如何实现线程同步

线程同步的本质上就是: 对临界资源做检查, 如果资源不就绪就等待, 资源一但就绪就将等待中的线程唤醒, 从而高效有序的进行访问, 避免不必要的锁资源浪费以及饥饿问题

对临界资源检查, 本质也是在访问临界资源, 所以也要互斥进行(信号量不需要, 后续会解释)

如果通过编码的方式实现线程同步:

1.条件变量

2.POSIX信号量

二.条件变量

本质上条件变量就是一个"等待-唤醒"的过程

1.常见接口以及使用

需要包头文件<pthread.h>

类型:

pthread_cond_t

定义方式:

与pthread_mutex_t的定义方式一样 (互斥量mutex详解, 传送入口: http://t.csdn.cn/ikHAk)

定义为全局 or 定义为静态: pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

定义为局部:

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

       int pthread_cond_destroy(pthread_cond_t *cond);

使用方式:

等待

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

唤醒

int pthread_cond_broadcast(pthread_cond_t *cond); (广播, 唤醒全部正在等待线程)
int pthread_cond_signal(pthread_cond_t *cond); (只唤醒一个)

2.wiat/signal中的第二个参数mutex的意义

对临界资源检查, 本质也是在访问临界资源, 所以也要互斥进行

在使用上, 一定是

pthread_mutex_lock(&mutex);

pthread_cond_wait(&cond, &mutex);

pthread_mutex_unlock(&mutex);

-------------------------------------------------

唤醒pthread_cond_signal(&cond);是否互斥进行看具体场景

在wait接口传入锁地址的意义:

如果一个持有锁的线程挂起等待了, 它是持有锁等待的, 会导致其他线程申请不到锁

所以pthread_cond_wait接口的内部实现, 是先解锁, 等到被唤醒的时候再加锁, 这样的一个设计

所以就需要传入mutex地址

3.代码验证

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

const static size_t THREAD_NUM = 3;

typedef void (*func_t)(const std::string &, pthread_mutex_t *, pthread_cond_t *);

volatile bool quit = false;

struct ThreadData
{
    ThreadData(const std::string &name, func_t func, pthread_mutex_t *mutex, pthread_cond_t *cond)
        : _name(name), _func(func), _mutex(mutex), _cond(cond)
    {
    }

    std::string _name;
    func_t _func;
    pthread_mutex_t *_mutex;
    pthread_cond_t *_cond;
};

void func1(const std::string &info, pthread_mutex_t *mutex, pthread_cond_t *cond)
{
    while (!quit)
    {
        pthread_mutex_lock(mutex);
        pthread_cond_wait(cond, mutex);
        if (!quit)
            std::cout << info << " -> "
                      << "func1" << std::endl;
        pthread_mutex_unlock(mutex);
        // sleep(1);
    }
}

void func2(const std::string &info, pthread_mutex_t *mutex, pthread_cond_t *cond)
{
    while (!quit)
    {
        pthread_mutex_lock(mutex);
        pthread_cond_wait(cond, mutex);
        if (!quit)
            std::cout << info << " -> "
                      << "func2" << std::endl;
        pthread_mutex_unlock(mutex);
        // sleep(1);
    }
}

void func3(const std::string &info, pthread_mutex_t *mutex, pthread_cond_t *cond)
{
    while (!quit)
    {
        pthread_mutex_lock(mutex);
        pthread_cond_wait(cond, mutex);
        if (!quit)
            std::cout << info << " -> "
                      << "func3" << std::endl;
        pthread_mutex_unlock(mutex);
        // sleep(1);
    }
}

void *entry(void *args)
{
    ThreadData *td = (ThreadData *)args;
    td->_func(td->_name, td->_mutex, td->_cond);

    delete td;
    return nullptr;
}

int main()
{
    pthread_t t[THREAD_NUM];
    func_t funcArr[THREAD_NUM] = {func1, func2, func3};

    pthread_mutex_t mutex;
    pthread_cond_t cond;
    pthread_mutex_init(&mutex, nullptr);
    pthread_cond_init(&cond, nullptr);

    // 创建线程
    // 让这些线程分别执行自己的函数
    for (size_t i = 0; i < THREAD_NUM; ++i)
    {
        std::string str = "thread ";
        str += std::to_string(i + 1);
        ThreadData *td = new ThreadData(str, funcArr[i], &mutex, &cond);
        pthread_create(t + i, nullptr, entry, (void *)td);
    }

    std::cout << "3秒后, 随机单独唤醒10次, 每次一秒" << std::endl;
    sleep(3);
    size_t count = 10;
    for (size_t i = 0; i < count; ++i)
    {
        std::cout << "正在唤醒中..." << std::endl;
        // pthread_cond_signal(&cond);
        pthread_cond_broadcast(&cond);
        sleep(1);
    }

    std::cout << "测试结束" << std::endl;

    quit = true;
    // 最后广播一次, 将所有线程再唤醒一次判断结束
    pthread_cond_broadcast(&cond);

    // 回收线程
    for (size_t i = 0; i < THREAD_NUM; ++i)
    {
        pthread_join(t[i], nullptr);
        std::cout << "线程: " << i + 1 << "已被回收" << std::endl;
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}

三.POSIX信号量

1.概念

POSIX信号量和System V信号量作用相同, 都是用于同步的访问共享资源的; 不同的是, POSIX可以用于线程同步

POSIX信号量的本质: 就是一个具有原子性的计数器, 其原子性由库给我们提供好了

对应的两套操作:

P操作 - 本质上是申请, 计数器--

V操作 - 本质上是释放, 计数器++

本质上POSIX信号量更像是一种对资源的预定机制

2.常见接口以及使用

需要包头文件<semaphore.h>

类型:

sem_t

定义方式:

int sem_init(sem_t* sem, int pshared, unsigned int value);

int sem_destory(sem_t* sem);

参数解释:

        pshared: 0表示线程间共享, 非0表示进程间共享

        value: 信号量初始值

使用方式:

P操作

int sem_wait(sem_t* sem);

V操作

int sem_post(sem_t* sem);

四.条件变量vsPOSIX信号量

实现线程同步可以有很多种方式

其中1.条件变量 2.POSIX信号量

那么什么时候用条件变量?什么时候用POSIX信号量呢?

实际上, 条件变量更倾向于不知道临界资源具体有多少, 如果临界资源不够了, 通过统一"等待"的方式来等待临界资源就绪; 而POSIX信号量, 是一种对临界资源的预定机制, 需要明确知道还有多少临界资源可供分配, 才能对计数器做具体的"-- or ++"操作, 即PV操作

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

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

相关文章

公链“私”用

发表时间&#xff1a;2022年3月17日 信息来源&#xff1a;bsvblockchain.org 虽然区块链行业已经诞生了十多年&#xff0c;但直到最近几年这个行业才开始走向专业化并日趋成熟&#xff0c;现在它已成为了一种可为企业实用范例提供合法解决方案的技术。 早年间&#xff0c;与这…

基于ubuntu20.4的wine的MDK5软件的安装

本文基于ubuntu20.4安装MDK5的keil软件&#xff0c;由于MDK不提供linux版本的安装软件&#xff0c;因此需要利用wine软件来安装MDK5软件&#xff0c;具体流程包括wine软件安装、MDK5安装及MDK5的lic添加等3部分内容。具体流程如下所示&#xff1a; &#xff08;一&#xff09;…

typescript 路径别名问题(别名设置 开发与打包时路径问题)

在使用TS开发时&#xff0c;一般我们会在tsconfig 中设置别名来让代码变得优雅一点。 将 import xx from …/…/…/service/改为import xx form ‘service/’ tsconfig 别名设置 此时项目结构如下 一般我们可以通过baseUrl 和path两个字段来设置对应的别名 {"include…

爬虫:栖落的电影网站,利用requests和re模块

这是栖落的电影网站地址&#xff1a;https://xxx.xxx 进入网页&#xff0c;显示&#xff1a; 爬取目标&#xff1a;电影的名称、观影人数和评分。 易知本网站的url url "https://xxx.xxx" 本网站会识别出headers中的python请求而拒绝访问&#xff0c;所以需要更改…

企业管理者不得不看!现在的大企业都是怎么做文档管理的?

最近有一位朋友问我&#xff1a;“如果是大型企业&#xff0c;文档该怎么管理&#xff1f;” 说实话&#xff0c;很多内部的CIO、CTO对这个问题都是束手无策。信息文件散乱、难以和内部的组织构架关联起来、查找困难、不同版本更新进度不一&#xff0c;确实存在一些管理上的难题…

【DSP视频教程】第11期:插补算法,曲线拟合丝滑顺畅,统计函数和基础函数加速实现,汇集SIMD,饱和和MAC乘累加应用实战(2023-02-12)

视频教程汇总帖&#xff1a;https://www.armbbs.cn/forum.php?modviewthread&tid110519 DSP视频教程有段时间没有更新了。 当前DSP库从CMSIS软件包里面独立出来&#xff0c;并且更新非常频繁&#xff0c;所以本期视频教程优先给大家简单介绍下新版DSP&#xff0c; 然后为…

物流运输管理系统源码:实现物流公司全链条管理

一套适用于物流公司的物流运输管理系统&#xff0c;涵盖物流公司内部从订单->提货->运单->配车->点到->预约->签收->回单->代收货款的全链条管理系统。 运行环境&#xff1a;Windows.NET4.0SQLSERVER2008R2 私信了解更多&#xff01; 菜单功能&#…

灰色关联分析法详解及python实践

1. 关于灰色关联分析 1.1. 什么是灰色关联分析 灰色关联分析是指对一个系统发展变化态势的定量描述和比较的方法&#xff0c;其基本思想是通过确定参考数据列和若干个比较数据列的几何形状相似程度来判断其联系是否紧密&#xff0c;它反映了曲线间的关联程度。 在系统发展过…

亚马逊云科技携手滴普科技,打造数据智能新标杆

随着企业数字化转型的不断深入&#xff0c;数据对于业务的价值和重要性也逐渐凸显。越来越多企业意识到&#xff0c;只有不断提升底层数据基础平台的性能和能力&#xff0c;才能构建数据驱动的业务&#xff0c;增强企业核心竞争力。作为湖仓一体数据智能基础软件独角兽企业&…

python基础之变量

Python 中的变量不需要声明。每个变量在使用前都必须赋值&#xff0c;变量赋值以后该变量才会被创建。 在 Python 中&#xff0c;变量就是变量&#xff0c;它没有类型&#xff0c;我们所说的"类型"是变量所指的内存中对象的类型。 1&#xff1a;什么是变量 变量指的…

纽扣电池上架亚马逊UL4200A检测项目流程

纽扣电池&#xff08;button cell &#xff09;也称扣式电池&#xff0c;是指外形尺寸象一颗小纽扣的电池&#xff0c;一般来说直径较大&#xff0c;厚度较薄。纽扣电池因体形较小&#xff0c;故在各种微型电子产品中得到了广泛的应用&#xff0c;直径从4.8mm至30mm&#xff0c…

腾讯云轻量应用服务器和云服务器CVM区别

腾讯云轻量级服务器和云服务器有什么区别&#xff1f;轻量级应用服务器和云服务器CVM哪个更好&#xff1f;无论成本和使用门槛如何&#xff0c;云服务器CVM都更好&#xff1b;从性价比和易用性的角度来看&#xff0c;轻量级应用服务器的成本更低。来详细谈谈腾讯云轻量级应用服…

LabVIEW项目或库文件已损坏

LabVIEW项目或库文件已损坏尝试打开项目文件&#xff08;*.lvproj&#xff09;时&#xff0c;不断收到错误&#xff1a;项目或库文件已损坏。当尝试在新计算机上打开类库时&#xff0c;看到错误内存或数据结构损坏。无法加载文件&#xff0c;但库在另一台计算机上打开正常。如何…

如何学习PMP?

★基础要打牢 方法&#xff1a;“基础不牢&#xff0c;地动山摇”&#xff0c;如果基础不牢那么就很难拿高分&#xff0c;因为连最基础的题目分都不一定能拿到。 可以在针对基础知识&#xff0c;把PMBOK看一两遍&#xff0c;再次加深印象&#xff0c;再把平时做章节练习、每日5…

Java导出自定义Excel表格,一套组合拳解决

&#x1f535; (一) 功能现状 &#x1f36d;目前大部分SpringBoot框架中自带了Excel导出功能&#xff0c;但其中并不支持自定义导出效果的可能性很大。比如很多框架中都能直接支持自动生成关于单表的增删改查操作的前后端代码&#xff0c;但是复杂的多表操作就无法做到&#xf…

c# winform错误大全

c# winform 错误大全为了实现安装包安装完成后&#xff0c;启动程序。System.BadImageFormatException: 未能加载文件或程序集“file:///C:\xxxxxxxxx\xxxxxxx.exe”或它的某一个依赖项。生成此程序集的运行时比当前加载的运行时新&#xff0c;无法加载此程The version of the …

软件功能测试包含了哪些测试项目?功能测试报告收费标准

一、软件功能测试是什么? 软件功能测试是测试人员通过执行功能测试用例逐步验证软件产品各项功能是否达到预期需求的测试过程。也是俗称的“点点点测试”&#xff0c;这是基础性的测试类型&#xff0c;软件产品的功能直接影响到用户体验&#xff0c;所以软件功能测试意义重大…

UDP的详细解析

UDP的详细解析 文章目录UDP的详细解析UDP 概述UDP的首部格式检验和的计算抓包测试参考TCP/IP运输层的两个主要协议都是互联网的正式标准&#xff0c;即&#xff1a;用户数据报协议UDP (User Datagram Protocol)传输控制协议TCP (Transmission Control Protocol) 按照OSI的术语…

【汽车电子】什么是ADAS?

文章目录ADAS——先进驾驶辅助系统ADAS——商用车安全性能提升的利器总结ADAS——先进驾驶辅助系统 ADAS&#xff0c;全称Advanced Driver Assistance Systems &#xff0c;“先进驾驶辅助系统”&#xff0c;adas是汽车上面的一种系统&#xff0c;中文名叫做高级驾驶辅助系统&…

Java集合框架常见面试题

1. 剖析面试最常见问题之 Java 集合框架 1.1. 集合概述 1.1.1. Java 集合概览1.1.2. 说说 List,Set,Map 三者的区别&#xff1f;1.1.3. 集合框架底层数据结构总结 1.1.3.1. List1.1.3.2. Set1.1.3.3. Map 1.1.4. 如何选用集合?1.1.5. 为什么要使用集合&#xff1f; 1.2. Colle…