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.mfbz.cn/a/470583.html

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

相关文章

前端项目,个人笔记(二)【Vue-cli - 引入阿里矢量库图标 + 吸顶交互 + setup语法糖】

目录 1、项目中引入阿里矢量库图标 2、实现吸顶交互 3、语法糖--<script setup> 3.1、无需return 3.2、子组件接收父组件的值-props的使用 3.3、注册组件 1、项目中引入阿里矢量库图标 步骤一&#xff1a;进入阿里矢量库官网中&#xff1a;iconfont-阿里巴巴矢量…

2023年中国电商市场研究报告

研究范畴界定为中国国内2C电商市场 ⚠️ 关键点&#xff1a; 流量红利减少&#xff0c;电商市场进入存量增量 竞争的发展阶段&#xff1b;新兴电商平台不断挑战现有头部电商平台行业地位&#xff1b;消费者更加趋于理性&#xff0c;更加关注低价和服务&#xff1b;市场趋势&…

vue3 搜索框 相关搜索内容 搜索词变色

html <!-- 搜索框 --> <div class"input"><input type"text" v-model"search_content" input"replace_text(search_content)"focus"search_show true, replace_text(search_content)" blur"search_s…

【爬虫】web自动化和接口自动化

专栏文章索引&#xff1a;爬虫 目录 一、介绍 二、推荐 1.接口自动化 2.Web自动化 一、介绍 爬虫技术一般可以分为两种类型&#xff1a;接口自动化和web自动化。下面是它们的简要介绍&#xff1a; 1.接口自动化 接口自动化技术的主要目的是通过模拟HTTP请求来实现自动化…

CSS 三大特性 详细讲解

CSS 三大特性及代码解释 层叠性 当相同选择器设置相同样式且发生冲突时&#xff0c;此时后者的样式会覆盖&#xff08;层叠&#xff09;前者冲突样式。CSS的层叠性就是用于解决样式冲突问题。 Input&#xff1a; <style>div {color: red;}div { color: blue; <!-…

第3章 物联网关键技术

物联网的核心是把物联到网上。物联网的系统架构自下而上分别是&#xff1a;底层——利用RFID等无线通信技术、传感器、二维码等随时随地获取物体的信息&#xff0c;感知世界的感知层主要完成信息的采集、转换和收集&#xff0c;中间层——用来传输数据的网络传输层&#xff0c;…

基础-笔试题6

1、tcp/udp是属于哪一层&#xff1f;tcp/udp有何优缺点&#xff1f; tcp /udp属于运输层 TCP 服务提供了数据流传输、可靠性、有效流控制、全双工操作和多路复用技术等。 与 TCP 不同&#xff0c; UDP 并不提供对 IP 协议的可靠机制、流控制以及错误恢复功能等。由于 UDP 比较…

基于nodejs+vue班级管理系统的设计与实现-flask-django-python-php

随着电子技术的普及和快速发展&#xff0c;线上管理系统被广泛的使用&#xff0c;有很多事业单位和商业机构都在实现电子信息化管理&#xff0c;班级管理系统也不例外&#xff0c;由比较传统的人工管理转向了电子化、信息化、系统化的管理。随着互联网技术的高速发展&#xff0…

【QT实现下载功能】通过request请求下载并保存到本地

想要做一个带有向http请求并获得内容功能的软件&#xff0c;通过寻找网上的示例&#xff0c;了解了想要实现这个功能&#xff0c;主要有几个部分要点&#xff1a; 1.联网&#xff08;要勾选添加QNetwork库&#xff09; 2.向http请求内容&#xff08;发request&#xff09; 3.获…

蓝桥杯刷题|04普及-真题

[蓝桥杯 2018 省 B] 螺旋折线 题目描述 如图所示的螺旋折线经过平面上所有整点恰好一次。 对于整点 (X,Y)&#xff0c;我们定义它到原点的距离 dis(X,Y) 是从原点到(X,Y) 的螺旋折线段的长度。 例如 dis(0,1)3&#xff0c;dis(−2,−1)9。 给出整点坐标 (X,Y)&#xff0c;你…

CentOS 7.9 常用环境配置

文章目录 环境准备安装docker安装Java安装maven安装git安装MYSQL安装Redis安装RabbitMq安装minio 环境准备 操作系统版本为centos 7.9&#xff0c;内核版本需要在3.10以上 sudo uname -rsudo cat /etc/redhat-release1.确认环境好后&#xff0c;安装工具包并设置仓库 sudo yum…

Android14音频进阶:AudioFlinger究竟如何混音?(六十三)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…

最新ChatGPT/GPT4科研应用与AI绘图及论文高效写作教程

原文链接&#xff1a;最新ChatGPT/GPT4科研应用与AI绘图及论文高效写作教程https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247598050&idx5&sn70fd3f5946d581ad9c1363295b130ef5&chksmfa823e05cdf5b713baf9cf1381bfb2455ad675a0b21e194bef8b76f35d6aa77…

轻巧的elasticsearch可视化工具

一、概述 常见的ES可视化工具有&#xff1a; kibanaelasticsearch-headElasticHDDejavucerebroelasticview 一、安装elasticview 在众多ES可视化龚居中&#xff0c;elasticview是一款比较轻量简洁&#xff0c;兼容性较好&#xff0c;可以兼容多个ES版本&#xff0c;不但可以进…

PySpark案例实战

一、前言介绍 二、基础准备 # 导包 from pyspark import SparkConf,SparkContext #创建SparkConf类对象 confSparkConf().setMaster("local[*]").setAppName("test_spark_app") #基于SparkXConf类对象创建SparkContext对象 scSparkContext(confconf) #打印…

PCL 极大似然估计法拟合平面

目录 一、算法原理1、极大似然估计2、拟合过程3、参考文献二、代码实现三、结果展示一、算法原理 1、极大似然估计 在以最小二乘法为基础的估计算法中, 所有的参数都是确定值;而实际上,测量数据与未知参数都具有一定的随机性。这就导致了最小二乘法估计质量的缺陷以及对白噪…

整型数组按个位值排序 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 给定一个非空数组(列表)&#xff0c;其元素数据类型为整型&#xff0c;请按照数组元素十进制最低位从小到大进行排序&#xff0c;十进制最低位相同的元素&#xf…

机器学习——编程实现从零构造训练集的决策树

自己搭建一棵决策树【长文预警】 忙了一个周末就写到了“构建决策树”这一步&#xff0c;还没有考虑划分测试集、验证集、“缺失值、连续值”&#xff0c;预剪枝、后剪枝的部分&#xff0c;后面再补吧&#xff08;挖坑&#xff09; 第二节内容&#xff1a;验证集划分\k折交叉…

线性代数基础概念和在AI中的应用

基本概念 线性代数是数学的一个分支&#xff0c;专注于向量、向量空间&#xff08;也称为线性空间&#xff09;、线性变换和矩阵的研究。这些概念在数据科学、人工智能、工程学和物理学等多个领域都有广泛应用。以下是这些基本概念的详细解释和它们在数据处理和AI中的应用。 …

Jackson 2.x 系列【2】生成器 JsonGenerator

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.17.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-seata-demo 文章目录 1. 前言2. 案例演示2.1 创建 JsonFactory2.2 创建 JsonGenerator2.3 写入操作2.4 查…