【Linux】线程互斥和同步

news2024/11/15 6:54:03

目录

线程互斥

相关概念

互斥量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

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

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

相关文章

【已解决】ModuleNotFoundError: No module named ‘numpy’

【已解决】ModuleNotFoundError: No module named ‘numpy’ 在Python编程中&#xff0c;遇到“ModuleNotFoundError: No module named ‘numpy’”这样的错误提示并不罕见。这个错误意味着Python解释器无法在你的环境中找到名为numpy的模块。numpy是Python中一个非常重要的库…

ElasticSearch(四)— 数据检索与查询

一、基本查询语法 所有的 REST 搜索请求使用_search 接口&#xff0c;既可以是 GET 请求&#xff0c;也可以是 POST请求&#xff0c;也可以通过在搜索 URL 中指定索引来限制范围。 _search 接口有两种请求方法&#xff0c;一种是基于 URI 的请求方式&#xff0c;另一种是基于…

JavaScript第一天

变量的基本使用 更新变量 let age 18age 19 用户名输入案例&#xff1a; let uname prompt(请输入姓名) document.write(uname) 这样在提示框中输入姓名之后&#xff0c;就会在网页中显示出来 当输入之后不在网页中显示的时候&#xff0c;可能是变量名写错了&#xf…

C++笔记---缺省参数和函数重载

1. 缺省参数 1.1 定义 缺省参数是声明或定义函数时为函数的参数指定一个缺省值&#xff08;默认值&#xff09;。在调用该函数时&#xff0c;如果没有指定实参 则采用该形参的缺省值&#xff0c;否则使用指定的实参&#xff0c;缺省参数分为全缺省和半缺省参数。 void Func(…

Linux源码阅读笔记14-IO体系结构与访问设备

IO体系结构 与外设通信通常称为输入输出&#xff0c;一般缩写为I/O。在实现外设IO的时候&#xff0c;内核必须处理三个可能出现的问题&#xff1a; 必须根据具体的设备类型和模型&#xff0c;使用各种方法对硬件寻址。内核必须向用户应用程序和系统工具提供访问各种设备的方法…

【Git多人协作开发】同一分支下的多人协作开发模式

目录 0.前言场景 1.开发者1☞完成准备工作&协作开发 1.1创建dev分支开发 1.2拉取远程dev分支至本地 1.3查看分支情况和分支联系情况 1.4创建本地dev分支且与远程dev分支建立联系 1.5在本地dev分支上开发file.txt 1.6推送push至远程仓库 2.开发者2☞完成准备工作&…

性能测试工具、负载测试工具、缺陷跟踪工具推荐

负载测试工具 - 有助于对站点或应用程序进行性能/负载测试 1&#xff09;WebLOAD WebLOAD 是一款出色的测试工具&#xff0c;提供了许多强大的脚本功能&#xff0c;有助于测试复杂场景。该工具支持从 Selenium 到移动端、从企业应用到网络协议的数百种技术。使用这款工具可以…

Java | Leetcode Java题解之第279题完全平方数

题目&#xff1a; 题解&#xff1a; class Solution {public int numSquares(int n) {if (isPerfectSquare(n)) {return 1;}if (checkAnswer4(n)) {return 4;}for (int i 1; i * i < n; i) {int j n - i * i;if (isPerfectSquare(j)) {return 2;}}return 3;}// 判断是否为…

nVisual综合布线预标签解决方案

1 跳线变更前编制标签编码、打印标签、粘贴标签费时费力&#xff1b; 2 无标签、假标签、标签错误问题造成后期无法查询线缆连接关系&#xff1b; 1 跳线出厂时在跳线两端预粘贴条码标签&#xff0c;为每条跳线设置唯一ID&#xff1b; 2 跳线变更完成后&…

MySQL练手 --- 1251. 平均售价

题目链接&#xff1a;1251. 平均售价 思路&#xff1a; 由题意可知&#xff0c;Prices表和UnitsSold表&#xff0c;表的连接关系为一对一&#xff0c;连接字段&#xff08;匹配字段&#xff09;为product_id 要求&#xff1a;查找每种产品的平均售价。而Prices表含有价格还有…

Vue与ASP.NET Core Web Api设置localhost与本地ip地址皆可访问

Vue的设置 我们创建并启动一个Vue项目&#xff0c;如下所示&#xff1a; 打开cmd&#xff0c;输入ipconfig查询本地ip地址&#xff1a; 想通过本地ip地址访问&#xff0c;把localhost改成本地ip地址&#xff0c;发现打不开&#xff1a; 这是因为Vue项目默认只有localhost&…

二级医院LIS系统源码,医学检验系统,支持DB2,Oracle,MS SQLServer等主流数据库

系统概述&#xff1a; LIS系统即实验室信息管理系统。LIS系统能实现临床检验信息化&#xff0c;检验科信息管理自动化。其主要功能是将检验科的实验仪器传出的检验数据经数据分析后&#xff0c;自动生成打印报告&#xff0c;通过网络存储在数据库中&#xff0c;使医生能够通过医…

Stage模型应用程序包结构

目录 官网地址 官网结构图 开发态包结构 工程目录结构 配置文件 module.json5配置文件 app.json5配置文件 官网地址 官网地址 包结构 官网结构图 开发态包结构 在DevEco Studio上创建一个项目工程&#xff0c;并尝试创建多个不同类型的Module&#xff08;类似一个一个的页…

基于区块链技术的高校教育资源共享的研究

&#xff08;一&#xff09;项目背景 时代变迁下的高教管理革新需求 当前&#xff0c;我国高等教育体系深受行政化管理模式影响&#xff0c;其在指引办学方向、资源优化配置及院校稳定上功不可没。然而&#xff0c;随着社会主义市场经济体系的深化发展&#xff0c;该模式逐渐显…

opencv - py_calib3d - py_calibration 相机校准

文章目录 Camera Calibration 相机校准目标基础知识代码设置校准去失真1. 使用 **cv.undistort()**2. 使用 **remapping** 重新投影误差 Camera Calibration 相机校准 目标 在本节中&#xff0c;我们将学习 相机造成的失真类型如何找到相机的内在和外在属性如何根据这些属性…

代码随想录——一和零(Leetcode474)

题目链接 0-1背包 class Solution {public int findMaxForm(String[] strs, int m, int n) {// 本题m&#xff0c;n为背包两个维度// dp[i][j]:最多右i个0和j个1的strs的最大子集大小int[][] dp new int[m 1][n 1];// 遍历strs中字符串for(String str : strs){int num0 …

通信原理-思科实验三:无线局域网实验

实验三 无线局域网实验 一&#xff1a;无线局域网基础服务集 实验步骤&#xff1a; 进入物理工作区&#xff0c;导航选择 城市家园; 选择设备 AP0&#xff0c;并分别选择Laptop0、Laptop1放在APO范围外区域 修改笔记本的网卡&#xff0c;从以太网卡切换到无线网卡WPC300N 切…

若依 ruoyi poi Excel合并行的导入

本文仅针对文字相关的合并做了处理 &#xff0c;图片合并及保存需要另做处理&#xff01;&#xff01; 目标&#xff1a;Excel合并行内容的导入 结果&#xff1a; 1. ExcelUtil.java 类&#xff0c;新增方法&#xff1a;判断是否是合并行 /*** 新增 合并行相关代码&#xff1a;…

文件解析的终极工具:Apache Tika

文件解析的终极工具&#xff1a;Apache Tika Apache Tika 简介 Apache Tika 是一个开源的、跨平台的库&#xff0c;用于检测、提取和解析各种类型文件的元数据。 它支持多种文件格式&#xff0c;包括文档、图片、音频和视频。 Tika是一个底层库&#xff0c;经常用于搜索引擎…

Windows下ORACLE数据泵expdp和impdp使用

Windows下ORACLE数据泵expdp和impdp使用 一、基础环境 操作系统&#xff1a;Windows server 2008&#xff1b; 数据库版本&#xff1a;Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production 数据库工具&#xff1a;PL/SQL 12.0.7 实验内容&…