【Linux】线程互斥和同步

目录

线程互斥

相关概念

互斥量mutex

互斥量的接口

初始化互斥量

销毁互斥量

互斥量加锁/解锁

可重入VS线程安全

概念

可重入与线程安全的联系

可重入与线程安全的区别

死锁

死锁的四个必要条件

避免死锁

避免死锁的算法

线程同步

条件变量

条件变量函数 初始化

销毁

等待条件满足

唤醒等待

CP问题 

代码实现


线程互斥

相关概念

  • 临界资源:多线程执行流共享的资源就叫临界资源。
  • 临界区:每个线程内部,访问临界资源的代码,就叫临界区。
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

互斥量mutex

        为了解决多个线程并发的操作共享变量所带来的问题,本质上就是需要一把锁,Linux中提供的锁被称为互斥量。

互斥量的接口

初始化互斥量

初始化互斥量有两种方法:

方法一:静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

方法二:动态分配:

int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                                        const pthread_mutexattr_t *restrict attr);
        参数:
                mutex:要初始化的互斥量
                attr:NULL

销毁互斥量

销毁互斥量时需要注意:

  • 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁。
  • 不要销毁一个已经加锁的互斥量。
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。

int pthread_mutex_destroy(pthread_mutex_t *mutex)

互斥量加锁/解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
……
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//返回值 : 成功返回 0, 失败返回错误号

调用 pthread_mutex_lock 时,会有以下几种情况:

  • 互斥量处于未锁的状态,该函数就会将互斥量锁定,同时返回成功。
  • 发起函数调用时,其他线程已经锁定了互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_mutex_lock调用就会就入阻塞(执行流被挂起),等待互斥量解锁。

可重入VS线程安全

概念

线程安全:多个线程并发同一段代码的时候,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,就会出现该问题。

重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数再重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入,否则就是不可重入函数。

可重入与线程安全的联系

  • 函数是可重入的,那就是线程安全的。
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全的也不是可重入的。

可重入与线程安全的区别

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

加锁的本质:用时间换取安全

加锁的表现:线程对于临界区代码串行执行

加锁原则:尽量的保证临界区代码,越少越好

在纯互斥的环境下,如果锁分配不够合理,容易导致其他线程的饥饿问题!当然不是说只要有互斥,必有饥饿。适合纯互斥的场景,就用互斥。

让所有的线程,获取锁按照一定的顺序,也就是按照一定的顺序获取资源--同步。

死锁

        死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。

        只有一把锁也是会出现死锁的情况,一个线程再持有锁的情况下,再申请锁,就会出现死锁。

死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用。(前提)
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已经获得的资源保持不放。(原则1)
  • 不剥夺条件:一个执行流已经获得的资源,再未使用完之前,不能强行剥夺。(原则2)
  • 循环等待条件:若干个执行流之间形成一种头尾相接的循环等待资源的关系。(重要条件)

避免死锁

  • 破坏死锁的四个必要条件(请求与保持/不剥夺/循环等待)
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

避免死锁的算法

  • 死锁检测算法
  • 银行家算法

线程同步

条件变量

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
  • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

条件变量函数 初始化

int pthread_cond_init(pthread_cond_t *restrict cond,
                                const pthread_condattr_t *restrict attr);
 参数:
           cond:要初始化的条件变量
           attr:NULL(条件变量的属性)

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,
                                pthread_mutex_t *restrict mutex);
参数:
        cond:要在这个条件变量上等待
        mutex:互斥量
pthread_cond_wait让线程等待的时候,会让线程释放持有的锁,也就是在等待之前,线程要先持有锁!!

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒在等待队列的全部线程
int pthread_cond_signal(pthread_cond_t *cond);//唤醒等待队列的第一个线程

CP问题 

consumer producter

在编写生产消费模型前,我们先梳理一下两者涉及的概念

三种关系

生产者 VS 生产者   互斥关系

消费者 VS 消费者   互斥关系

生产者 VS 消费者   互斥关系,只有互斥可能会导致消费者饥饿问题,所有还要有同步

两种角色

        生产者和消费者

一个交易场所

        特定结构的内存空间

优点

  • 支持忙闲不均
  • 生产和消费进行解耦

代码实现

blockqueue.hpp

#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>
template <class T>
class BlockQueue
{
    static const int defaultnum = 5;
    static int data=0;
public:
    BlockQueue(int maxcap = defaultnum)
        :maxcap_(maxcap)
    {
        phtread_mutex_init(&mutex_,nullptr);
        pthread_cond_init(&c_cond_,nullptr);
        pthread_cond_init(&p_cond_,nullptr);

        low_water_ = maxcap_/3;
        high_water_ = (maxcap_*2)/3;
    }

    T &pop()
    {
        pthread_mutex_lock(&mutex_);
        //这里有while循环防止伪唤醒的情况
        while(q.size() == 0)
        {
            //没有东西了,消费者去排队挂起
            pthread_cond_wait(&c_cond_,&mutex_);
        }
        T out = q.pop();
        //消费了一个,一定有空间给生产者生产,所以这里唤醒生产者
        if(q.size()<=low_water_)
            pthread_cond_signal(&p_cond_);
        pthread_mutex_unlock(&mutex_);

        return out;
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&mutex_);
        //这里有while循环防止伪唤醒的情况
        while(q.size() == maxcap_)
        {
            //1.调用的时候自动释放锁
            //2.生产的到达极值不能继续生产了
            pthread_cond_wait(&p_cond_,&mutex_);
        }
        //1.队列没满 2.被唤醒
        q.push(in);
        //生产了一个,可以通知消费者来消费了
        if(q.size()>=high_water_)
            pthread_cond_signal(&c_cond_);
        pthread_mutex_unlock(&mutex_)
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&c_cond_);
        pthread_cond_destroy(&p_cond_);

    }
private:
    std::queue<T> q_;
    int maxcap_;        //极值,到多少就不生产了
    int low_water_;
    int high_water_;    
    pthread_mutex_t mutex_;
    pthread_cond_t c_cond_; //消费者条件变量
    pthread_cond_t p_cond_; //生产者条件变量
    

};

main.cc

#include "BlockQueue.hpp"

void *Consumer(void *args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int>*>(args);
    while(true)
    {
        //消费
        int data = bq->pop();
        std::cout<<"消费了一个数据:"<< data <<std::endl;
    }
}
void *Productor(void *args)
{
     BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);
    while(true)
    {
        //生产
        data++;
        bq->push(data);
        std::cout<<"生产了一个数据:"<<data<<std::end;
    }
}
int main()
{
    BlockQueue<int> *bq = new BlockQueue<int>();
    pthread_t c,p;
    pthread_create(&c,nullptr,Consumer,bq);
    pthread_create(&p,nullptr,Productor,bq);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    delete bq; 
    return 0;
}

makefile

test_blockqueue:main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clea:
	rm -f test_blockqueue

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

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

相关文章

WebRTC音视频-环境搭建

目录 期望效果 1:虚拟机和系统安装 2:WebRTC客户端环境搭建 2.1&#xff1a;VScode安装 2.2&#xff1a;MobaXterm安装 3:WebRTC服务器环境搭建 3.1&#xff1a;安装openssh服务器 3.2&#xff1a;安装Node.js 3.3&#xff1a;coturn穿透和转发服务器 3.3.1&a…

redis服务器同 redis 集群

搭建redis服务器 修改服务运行参数 常用命令常用命令 创建redis集群 准备做集群的主机&#xff0c;不允许存储数据、不允许设置连接密码 配置服务器&#xff1a; 1、在任意一台redis服务器上都可以执行创建集群的命令。 2、--cluster-replicas 1 给每个master服务器分配1台…

基于java的设计模式学习

PS &#xff1a;以作者的亲身来看&#xff0c;这东西对于初学者来说有用但不多&#xff0c;这些东西&#xff0c;更像一种经验的总结&#xff0c;在平时开发当中一般是用不到的&#xff0c;因此站在这个角度上用处不大。 1.工厂模式 1.1 简单工厂模式 我们把new 对象逻辑封装…

[图解]《分析模式》漫谈16-“我用的”不能变成“我的”

1 00:00:00,720 --> 00:00:02,160 今天&#xff0c;我们来说一下 2 00:00:02,170 --> 00:00:04,850 “我用的”不能变成“我的” 3 00:00:04,860 --> 00:00:11,390 《分析模式》的前言 4 00:00:12,260 --> 00:00:13,410 有这么一句话 5 00:00:14,840 --> 0…

增加、动态修改、批量删除

deptId插入部门号&#xff08;前端遍历部门表使用下拉框&#xff0c;选择部门名&#xff0c;后端存入对应的部门号&#xff09; 写更新字段时&#xff0c;可以写出所有字段&#xff0c;用动态SQL进行判断有没有 只有String类型的需要判断不等于空字符串&#xff0c;其他只需判…

P3-AI产品经理-九五小庞

AI产品的数据流向 美团外卖&#xff0c;实时只能调度 美团28分钟送达需求的分析 AI产品常用的算法 常用算法 常见的AI算法解析 自然语言生成NLG语音识别&#xff1a;科大讯飞&#xff0c;通义千问 虚拟现实机器学习平台 决策管理系统生物特征识别技术 RPA(机器人流程自动…

2008-2023年上市公司环境绩效、资源消耗及排放明细表数据

2008-2023年上市公司环境绩效、资源消耗及排放明细表数据 1、时间&#xff1a;2008-2023年 2、来源&#xff1a;上市公司社会责任报告&#xff0c;上市公司年报 3、指标&#xff1a;证券代码、证券简称、上市公司ID、统计截止日期、披露内容类型、项目名称、项目数值、单位 …

Arduino学习笔记1——IDE安装与起步

一、IDE安装 去浏览器直接搜索Arduino官网&#xff0c;点击Software栏进入下载界面&#xff0c;选择Windows操作系统&#xff1a; 新版IDE下载不需要提前勾选所下载的拓展包&#xff0c;下载好后直接点击安装即可。 安装好后打开Arduino IDE&#xff0c;会自动开始下载所需的…

3.3-LSTM的改进

文章目录 1改进点1.1多层化1.2 dropout1.2.1具体概念1.2.2应该插入到LSTM模型的哪里 1.3权重共享 2改进之后的LSTMLM的代码实现2.1初始化2.2前向计算2.3反向传播 3相应的学习代码的实现4总结 1改进点 1.1多层化 加深神经网络的层数往往能够学习更复杂的模式&#xff1b;因此这…

【转盘案例-弹框-修改Bug-完成 Objective-C语言】

一、我们来看示例程序啊 1.旋转完了以后,它会弹一个框,这个框,是啥, Alert 啊,AlertView 也行, AlertView,跟大家说过,是吧,演示过的啊,然后,我们就用iOS9来做了啊,完成了以后,我们要去弹一个框, // 弹框 UIAlertController *alertController = [UIAlertContr…

数据结构(栈)

文章目录 一、概念与结构 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶&#xff0c;另⼀端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压…

在qt的c++程序嵌入一个qml窗口

//拖拽一个QQuickWidget c端和qml通信的桥梁 找到qml的main.qml的路径 ui->quickWidget->setSource(QUrl::fromLocalFile("../../../code/main.qml"));// QML 与 Qt Widgets 通信//窗口就成了一个类实例对象pRoot (QObject*)ui->quickWidget->rootObje…

操作系统:408考研|王道|学习笔记

系列目录 计算机组成原理 学习笔记I 计算机组成原理 学习笔记II 目录 系列目录第1章 计算机系统概述1.1 操作系统的基本概念1.1_1 操作系统的定义、功能1.1_2 操作系统的特征 1.2 操作系统的发展与分类1.3 操作系统的运行环境&#x1f31f;1.3_1 操作系统的运行机制&#x1f3…

防溺水预警系统引领水域安全新篇章

一、系统概述 随着人们对水域活动的需求增加&#xff0c;溺水事故频发&#xff0c;给人们的生命安全带来了严重威胁。然而&#xff0c;如今&#xff0c;一项创新科技正在以强大的功能和无限的潜力引领着水域安全的新篇章。智能防溺水预警系统&#xff0c;作为一种集成了智能感知…

SwiftUI 5.0(iOS 17)滚动视图的滚动目标行为(Target Behavior)解惑和实战

概览 在 SwiftUI 的开发过程中我们常说&#xff1a;“屏幕不够&#xff0c;滚动来凑”。可见滚动视图对于超长内容的呈现有着多么秉轴持钧的重要作用。 这不&#xff0c;从 SwiftUI 5.0&#xff08;iOS 17&#xff09;开始苹果又为滚动视图增加了全新的功能。但是官方的示例可…

不懂商业模式,没有互联网思维,不建议创业

在当今商业浪潮中&#xff0c;作为企业的掌舵人&#xff0c;若对商业模式缺乏深刻理解&#xff0c;确实容易陷入亏损与负债的困境&#xff0c;尤其是在互联网与创业领域。成功的企业往往建立在坚实的商业模式之上&#xff0c;这里我将为您概述五种关键模式&#xff0c;助力您把…

【go】Excelize处理excel表 带合并单元格、自动换行与固定列宽的文件导出

文章目录 1 简介2 相关需求与实现2.1 导出带单元格合并的excel文件2.2 导出增加自动换行和固定列宽的excel文件 1 简介 之前整理过使用Excelize导出原始excel文件与增加数据校验的excel导出。【go】Excelize处理excel表 带数据校验的文件导出 本文整理使用Excelize导出带单元…

Android 消息发布订阅框架:EventBus

目录 1.是什么 2.如何使用 3.五种线程模型 4.Eventbus2和Eventbus3的区别 一、是什么 EventBus是一款发布/订阅事件总线的框架&#xff0c;使用它可以进行模块间通信、解耦。它可以使用很少的代码&#xff0c;来实现多组件之间的通信&#xff0c;非常的方便。 为什么使用它…

十七、(正点原子)Linux LCD驱动

一、Framebuffer设备 在 Linux 中应用程序通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片等信息。 先来看一下裸机 LCD 驱动如下&#xff1a; ①、初始化 I.MX6U 的 eLCDIF 控制器&#xff0c;重点是 LCD 屏幕宽(width)、高(height)、 hspw、 hbp、 hfp、 vspw…