Linux之线程同步

目录

一、问题引入

二、实现线程同步的方案——条件变量

1、常用接口:

2、使用示例


一、问题引入

我们再次看看上次讲到的多线程抢票的代码:这次我们让一个线程抢完票之后不去做任何事。

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <time.h>
#include <pthread.h>

using namespace std;
#define THREAD_NUM 5

class threaddata
{
public:
    threaddata(const string &s, pthread_mutex_t *m)
        : name(s), mtx(m)
    {}

public:
    string name;
    pthread_mutex_t *mtx;
};

int ticket = 100;

void *getticket(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        pthread_mutex_lock(td->mtx); 
        if (ticket > 0)              
        {
            usleep(rand() % 10000);
            cout << td->name << ":"
                 << " " << ticket << endl;
            ticket--;
            pthread_mutex_unlock(td->mtx); 
        }
        else
        {
            pthread_mutex_unlock(td->mtx); 
            break;
        }
    }
    delete td;
    return nullptr;
}

int main()
{
    pthread_mutex_t mtx;
    pthread_mutex_init(&mtx, nullptr);

    pthread_t t[THREAD_NUM];
    for (int i = 0; i < THREAD_NUM; i++)
    {
        string name = "thread ";
        name += to_string(i + 1);
        threaddata *td = new threaddata(name, &mtx);
        pthread_create(t + i, nullptr, getticket, (void *)td);
    }

    for (int i = 0; i < THREAD_NUM; i++)
        pthread_join(t[i], nullptr);

    pthread_mutex_destroy(&mtx);

    return 0;
}

运行结果:

我们这就发现了一个问题,对于抢票系统,我们看到的是只有一个线程5在一直连续抢票,没有其他的线程。这很不合理。

这是因为如果个别线程的竞争力特别强,每次都能够申请到锁,但申请到锁之后什么也不做,所以在我们看来这个线程就一直在申请锁和释放锁,那么它就可以一直抢票。这就可能导致其他线程长时间竞争不到锁,造成了其他线程的饥饿问题(无法抢票)。虽然,你是拿到锁后再去访问临界资源,并且最后还释放了锁,由于竞争能力太强,可以一直拿到锁,这没有错,但这不合理。

为了解决这个问题,我们增加一个限制:当一个线程释放锁后,这个线程不能立马再次申请锁,该线程必须排到这个锁的资源等待队列的最后。这样,我们就有了线程同步:我们在保证数据安全的情况下让这些线程按照一定的顺序进行临界资源的访问,这就是线程同步。

竞态条件:因为时序问题,而导致程序异常,我们称为竞态条件。

二、实现线程同步的方案——条件变量

当一个线程互斥地访问某个变量时,它可能发现在其他线程改变状态之前,它什么也做不了

例如一个线程访问队列时,发现队列为空,它只能等待,直到其他线程将一个节点添加到队列中。这种情况就需要用到条件变量。

1、常用接口:

1、条件变量的定义和初始化

​
NAME
       pthread_cond_destroy, pthread_cond_init - destroy and initialize condition variables

SYNOPSIS
       #include <pthread.h>

       //销毁
       int pthread_cond_destroy(pthread_cond_t *cond);
       //初始化
       int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);
       //全局和静态变量初始化
       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2、线程等待临界资源: 

pthread_cond_wait 功能:一就是让线程在特定的条件变量下等待,二就是让线程在等待时释放对应的互斥锁。当线程被唤醒时,该函数会帮助我们线程获取锁。

#include <pthread.h>
//特定时间阻塞等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex,
       const struct timespec *restrict abstime);
//等待
int pthread_cond_wait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex);

 3、唤醒线程去访问临界资源

#include <pthread.h>
// 唤醒所有等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);

// 唤醒一个线程
int pthread_cond_signal(pthread_cond_t *cond);

注:1、条件变量通常需要配合互斥锁一起使用。

2、条件变量的使用:一个线程等待条件变量的条件成立而被挂起;另一个线程使条件成立后唤醒等待的线程。

3、等待的时候往往是在临界区内等待的。(加锁与解锁之间的区域进行等待)

4、线程被唤醒,是在之前进行等待的地方被唤醒。

2、使用示例

有了线程同步,我们就可以改进我们之前的抢票系统的代码:

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

using namespace std;
#define THREADNUM 3
typedef void *(*func)(void *argc);

class threaddata
{
public:
    threaddata(pthread_mutex_t *mtx, pthread_cond_t *cond, const string &name)
        : mtx_(mtx), cond_(cond), name_(name)
    {}

public:
    pthread_mutex_t *mtx_;
    pthread_cond_t *cond_;
    string name_;
};

int ticket = 100;

void *getticket(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        pthread_mutex_lock(td->mtx_);
        pthread_cond_wait(td->cond_, td->mtx_); // 在加锁和解锁之间进行等待
        if (ticket > 0)
        {
            usleep(rand() % 10000);
            cout << td->name_ << ":"
                 << " " << ticket << endl;
            ticket--;
            pthread_mutex_unlock(td->mtx_);
        }
        else
        {
            pthread_mutex_unlock(td->mtx_);
            break;
        }
    }
    delete td;
    return nullptr;
}

void *fun1(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        getticket((void *)td);
        sleep(1);
    }
}

void *fun2(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        getticket((void *)td);
        sleep(1);
    }
}

void *fun3(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        getticket((void *)td);
        sleep(1);
    }
}

int main()
{
    srand((unsigned long)time(nullptr) ^ getpid() ^ 433);
    pthread_mutex_t mtx;
    pthread_cond_t cond;
    pthread_mutex_init(&mtx, nullptr);
    pthread_cond_init(&cond, nullptr);
    pthread_t t[THREADNUM];
    func fun[THREADNUM] = {fun1, fun2, fun3};

    for (int i = 0; i < THREADNUM; i++)
    {
        string name = "thread ";
        name += to_string(i + 1);
        threaddata *td = new threaddata(&mtx, &cond, name);
        pthread_create(t + i, nullptr, fun[i], (void *)td);
    }

    sleep(5);

    while (true)
    {
        pthread_cond_signal(&cond);
        sleep(1);
    }

    for (int i = 0; i < THREADNUM; i++)
        pthread_join(t[i], nullptr);

    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);

    return 0;
}

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

此时我们会发现唤醒这三个线程时具有明显的顺序性,因为这些线程启动时默认都会在该条件变量下去等待,而我们每次都唤醒的是在当前条件变量下等待的头部线程,当该线程执行完代码后会继续排到等待队列的尾部进行等待。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PB-03F模组蓝牙基础+主从机指令的使用

文章目录 前言一、蓝牙基础指令1. ATBLEMAC 设置和查询蓝牙 MAC 地址2. ATBLEMODE 查询和设置蓝牙模式3. ATBLERFPWR 蓝牙设置或查询发射功率4. ATBLESTATE 查询连接状态5. ATBLEDISCON 断开蓝牙连接6. ATBLEMTU 查询或者设置 MTU7. ATBLESEND 向蓝牙透传通道发送数据8. ATTRAN…

mapstruct学习笔记-pojo之间的转换

1、前言 mapstruct中常用注解如Mapping,AfterMapping,BeanMapping等的使用,通过案例说明各式各样的业务pojo对象之间如何借助mapstruct完成相互之间的转换,减少代码量的同时也能突出业务逻辑流程,让你的代码里写起来更有规范可言。 2、简介 Reference Guide – MapStruct 3…

1-postgresql数据库高可用脚本详解

问题&#xff1a; pgrep -f postgres > /dev/null && echo 0 || pkill keepalived 这是什么意思 建议换成 pgrep -f postmaster > /dev/null && echo 0 || pkill keepalived 回答 这条命令是一个复合命令&#xff0c;包含条件执行和重定向的元素。让我们…

网站引用图片但它域名被墙了或者它有防盗链,我们想引用但又不能显示,本文附详细的解决方案非常简单!

最好的办法就是直接读取图片文件&#xff0c;用到php中一个常用的函数file_get_contents(图片地址)&#xff0c;意思是读取远程的一张图片&#xff0c;在输出就完事。非常简单&#xff5e;话不多说&#xff0c;直接上代码 <?php header("Content-type: image/jpeg&quo…

rviz上不显示机器人模型(模型只有白色)

文档中的是base_footprint&#xff0c;需要根据自己所设的坐标系更改&#xff0c;我的改为base_link 如何查看自己设的坐标系&#xff1a; 这些parent父坐标系就是 同时打开rviz后需要更改成base_link

openEuler 欧拉系统nginx正向代理 http https —— 筑梦之路

正向代理 Nginx正向代理&#xff0c;通过服务器代理客户端去重定向请求访问到目标服务器的一种代理服务。对于目标服务器来说浏览器/客户端是隐藏的。Nginx 正向代理默认只支持http 协议&#xff0c;不支持 https 协议&#xff0c;需借助"ngx_http_proxy_connect_module&q…

【数据结构取经之路】栈

目录 引言 栈的性质 顺序栈 栈的基本操作 初始化 销毁 插入 删除 判空 取栈顶元素 栈的大小 完整代码&#xff1a; 引言 栈(stack)&#xff0c;可以用数组实现&#xff0c;也可以用链表实现。用数组实现的栈叫顺序栈&#xff0c;用链表实现的栈叫链式栈&#…

使用uniapp,uni-data-select组件时,内容长度没超过容器宽度时候虽然能显示全内容但是数据后边会出现三个点,逼死强迫症

项目场景&#xff1a; 微信小程序开发&#xff0c;使用uniapp&#xff0c;uni-data-select组件时&#xff0c;内容长度没超过容器宽度时候虽然能显示全内容但是数据后边会出现三个点&#xff0c;逼死强迫症 解决方案&#xff1a; 找到组件的源代码&#xff0c;然后删除那三个…

鸿蒙实战开发:【FaultLoggerd组件】讲解

简介 Faultloggerd部件是OpenHarmony中C/C运行时崩溃临时日志的生成及管理模块。面向基于 Rust 开发的部件&#xff0c;Faultloggerd 提供了Rust Panic故障日志生成能力。系统开发者可以在预设的路径下找到故障日志&#xff0c;定位相关问题。 架构 Native InnerKits 接口 Si…

英伟达深夜放王炸|字节跳动游戏之路波折不断|文旅短剧风口将至|25岁QQ魅力不减,5亿人在用|云计算市场疯长152%|电商巨头齐瞄向富足悠闲银发族

新闻一分钟速览 文旅短剧风口将至&#xff0c;一地狂拍十部&#xff0c;影视界看法分歧&#xff0c;悬念丛生&#xff01;字节跳动游戏之路波折不断&#xff0c;能否逆风翻盘引关注。折叠屏手机痛症治愈&#xff0c;实力席卷高端市场&#xff0c;势头强劲&#xff01;雷军豪言…

刷题日记:面试经典 150 题 DAY6

刷题日记&#xff1a;面试经典 150 题 DAY6 392. 判断子序列167. 两数之和 II - 输入有序数组11. 盛最多水的容器15. 三数之和209. 长度最小的子数组 392. 判断子序列 原题链接 392. 判断子序列 双指针&#xff0c;i指向s&#xff0c;j指向t 如果s[i]t[j]&#xff0c;则匹配…

SCI一区 | Matlab实现GWO-TCN-BiGRU-Attention灰狼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现GWO-TCN-BiGRU-Attention灰狼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现GWO-TCN-BiGRU-Attention灰狼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述程序…

F-logic DataCube3 任意文件上传漏洞复现(CVE-2024-25832)

0x01 产品简介 F-logic DataCube3是一款用于光伏发电系统的紧凑型终端测量系统。 0x02 漏洞概述 F-logic DataCube3 /admin/setting_photo.php接口处存在任意文件上传漏洞 ,未经身份验证的攻击者可通过该漏洞在服务器端写入后门,获取服务器权限,进而控制整个web服务器。 …

【C++】用红黑树模拟实现set、map

目录 前言及准备&#xff1a;一、红黑树接口1.1 begin1.2 end1.3 查找1.4 插入1.5 左单旋和右单旋 二、树形迭代器&#xff08;正向&#xff09;2.1 前置 三、模拟实现set四、模拟实现map 前言及准备&#xff1a; set、map的底层结构是红黑树&#xff0c;它们的函数通过调用红…

学习笔记--强化学习(1)

参考&#xff1a;https://blog.csdn.net/koulongxin123/article/details/122676149 1.什么是强化学习&#xff1f; (1)定义 基于环境的反馈而行动&#xff0c;通过不断与环境的交互、试错&#xff0c;最终完成特定目的或者使得整体行动收益最大化&#xff08;是一种通过与环境…

使用jQuery的autocomplete实现数据查询一次,联想自动补全

书接上回&#xff0c;上次说到在jsp页面中&#xff0c;通过监听输入框的数值变化&#xff0c;实时查询数据库&#xff0c;得到返回值使用autocomplete属性自动补全&#xff0c;实现一个联想补全辅助操作&#xff0c;链接&#xff1a;使用jquery的autocomplete属性实现联想补全操…

Apache Dolphinscheduler - 无需重启 Master-Server 停止疯狂刷日志解决方案

记录的是一个 3.0 比较难搞的问题&#xff0c;相信不少使用过 3.0 的用户都遇到过 Master 服务中存在一些工作流或者任务流一直不停的死循环的问题&#xff0c;导致疯狂刷日志。不过本人到现在也没找到最关键的触发原因&#xff0c;只是看到一些连锁反应带来的结果…… 影响因素…

Linux下安装Android Studio及创建桌面快捷方式

下载 官网地址&#xff1a;https://developer.android.com/studio?hlzh-cn点击下载最新版本即可 安装 将下载完成后文件&#xff0c;进行解压&#xff0c;然后进入android-studio-2023.2.1.23-linux/android-studio/bin目录下&#xff0c;启动studio.sh即可为了更加方便的使…

【论文阅读】Improved Denoising Diffusion Probabilistic Models

Improved Denoising Diffusion Probabilistic Models 文章目录 Improved Denoising Diffusion Probabilistic Models概述Improving the Log-likelihoodLearning ∑ θ ( x t , t ) \sum_{\theta}(x_{t}, t) ∑θ​(xt​,t)Improving the Noise ScheduleReducing Gradient Nois…

Redis的安装和部署教程(Windows环境)

一、安装Redis服务 1、下载Redis压缩包 以下这个是我网盘里面的&#xff08;这个是v8.0版本的&#xff0c;支持导入.rdb数据文件&#xff09; 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;x0f1 --来自百度网盘超级会员V5的分享 2、解压到文件夹 将下载的压缩…
最新文章