Linux——线程同步互斥(线程安全)

线程互斥

进程线程间的互斥相关背景概念

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

这里的临界资源,如一个全局变量被多个线程所访问,但是这也导致了一个情况,这个临界资源是不安全的 ,如果我们想让多个线程对这个临界资源进行修改数据的操作,比如减一,假设这里要减到0就停止了,但是真实的情况就是在多个线程的同时访问下,这个临界资源数值最后会被减到负数,所以这个时候就要一个概念–互斥锁

#include<iostream>
#include<cstdio>
#include<cstring>
#include<pthread.h>
#include<string>
#include <unistd.h>
using namespace std;

// 操作共享变量会有问题的售票系统代码
int ticket = 100;
void *route(void *arg)
{
    char *id = (char *)arg;
    while (1)
    {
        if (ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
}
int main(void)
{
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, NULL, route, (void*)"thread 1");
    pthread_create(&t2, NULL, route, (void*)"thread 2");
    pthread_create(&t3, NULL, route, (void*)"thread 3");
    pthread_create(&t4, NULL, route, (void*)"thread 4");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
}

在这里插入图片描述

互斥量mutex

  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个
    线程,其他线程无法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之
    间的交互。
  • 多个线程并发的操作共享变量,会带来一些问题。

互斥量的接口

初始化互斥量

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

  • 方法1,静态分配:

补充:这里用作全局变量,PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

  • 方法2,动态分配:
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_ lock 时,可能会遇到以下情况:

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

引入互斥锁的抢票系统

#include<iostream>
#include<cstdio>
#include<cstring>
#include<pthread.h>
#include<string>
#include <unistd.h>
using namespace std;

int tickets = 10000; // 加锁保证共享资源的安全
pthread_mutex_t mutex; // 互斥锁

void *threadRoutine(void *name)
{
    string tname = static_cast<const char*>(name);

    while(true)
    {
        pthread_mutex_lock(&mutex); // 所有线程都要遵守这个规则
        if(tickets > 0) // tickets == 1; a, b, c,d
        {
            //a,b,c,d
            usleep(2000); // 模拟抢票花费的时间
            cout << tname << " get a ticket: " << tickets-- << endl;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
        }

        // 后面还有动作
        usleep(1000); //充当抢完一张票,后续动作
    }

    return nullptr;
}

int main()
{
    pthread_mutex_init(&mutex, nullptr);

    pthread_t t[4];
    int n = sizeof(t)/sizeof(t[0]);
    for(int i = 0; i < n; i++)
    {
        char *data = new char[64];
        snprintf(data, 64, "thread-%d", i+1);
        pthread_create(t+i, nullptr, threadRoutine, data);
    }

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

    pthread_mutex_destroy(&mutex);
    return 0;
}

在这里插入图片描述

可重入VS线程安全

概念
  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,
    并且没有锁保护的情况下,会出现该问题。
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们
    称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重
    入函数,否则,是不可重入函数。
常见的线程不安全的情况
  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数
常见的线程安全的情况
  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
  • 类或者接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性
常见不可重入的情况
  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构
常见可重入的情况
  • 不使用全局变量或静态变量
  • 不使用用malloc或者new开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都有函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
可重入与线程安全联系
  • 函数是可重入的,那就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。
可重入与线程安全区别
  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的

常见锁概念

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

死锁四个必要条件

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

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

避免死锁算法

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

线程同步

条件变量

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

同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问
    题,叫做同步
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

条件变量函数 初始化

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

调用pthread_cond_init函数初始化条件变量叫做动态分配,除此之外,我们还可以用下面这种方式初始化条件变量,该方式叫做静态分配:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

销毁
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
**cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释**

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

区别:

  • pthread_cond_signal函数用于唤醒等待队列中首个线程。
  • pthread_cond_broadcast函数用于唤醒等待队列中的全部线程。

参数说明

  • cond:唤醒在cond条件变量下等待的线程。

返回值说明

  • 函数调用成功返回0,失败返回错误码。

接口使用示例代码

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

using namespace std;
const int num = 5;

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *active(void *args)
{
    string name = static_cast<const char *>(args);
    while (true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex); // pthread_cond_wait,调用的时候,会自动释放锁, TODO
        cout << name << " 活动" << endl;
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    pthread_t tids[num];
    for (int i = 0; i < num; i++)
    {
        char *name = new char[32];
        snprintf(name, 32, "thread-%d", i + 1);
        pthread_create(tids + i, nullptr, active, name);
    }

    sleep(3);

    while (true)
    {
        cout << "main thread wakeup thread..." << endl;
        // pthread_cond_signal(&cond);
        pthread_cond_broadcast(&cond);

        sleep(1);
    }

    for (int i = 0; i < num; i++)
    {
        pthread_join(tids[i], nullptr);
    }
}

用pthread_cond_broadcast(&cond)同时时唤醒线程的效果
在这里插入图片描述
用pthread_cond_signal(&cond)一个一个唤醒的效果
在这里插入图片描述

条件变量使用的一般格式

等待条件变量

pthread_mutex_lock(&mutex);
while (条件为假)
	pthread_cond_wait(&cond, &mutex);
修改条件
pthread_mutex_unlock(&mutex);

唤醒等待线程

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

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

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

相关文章

K倍区间 刷题笔记

法一 前缀和暴力搜索 &#xff08;数据大会超时&#xff09; #include<iostream> #include<cstring> #include<algorithm> #include<cstdio> using namespace std; const int N100010; int a[N],s[N]; int n,k; int main(){ cin>>n>>…

第3部分 原理篇3可验证凭证(VC)(1)

3.3. 可验证凭证 3.3.1. 本节内容概述 本聪老师&#xff1a;今天开始去中心化身份中另一个最重要的概念可验证凭证&#xff08;verifiable credential&#xff09;的学习。凭证&#xff0c;也就是证件&#xff0c;在人类生活中不可或缺。可验证凭证实现了凭证的机器可读、加密…

微信小程序(五十一)页面背景(全屏)

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.页面背景的基本写法 2.去除默认上标题实习全屏背景 3. 背景适配细节 源码&#xff1a; index.wxss page{/* 背景链接 */background-image: url(https://pic3.zhimg.com/v2-a76bafdecdacebcc89b5d4f351a53e6a_…

嵌入式开发的常用软件、学习资源网站推荐

1、软件推荐 1.1、文本编辑软件 ——Notepad 1、适合编写和查看文本文件&#xff0c;也可以安装插件来查看二进制文件、对比文件 2、参考博客&#xff1a;《Notepad实用小技巧》&#xff1b; 1.2、PDF文件阅读软件——福昕PDF阅读器 福昕PDF阅读器&#xff0c;在官网就可以下载…

部署YOLOv8模型的实用常见场景

可以的话&#xff0c;GitHub上点个小心心&#xff0c;翻不了墙的xdm&#xff0c;csdn也可以点个赞&#xff0c;谢谢啦 车流量检测&#xff08;开源代码github&#xff09;&#xff1a; test3 meiqisheng/YOLOv8-DeepSORT-Object-Tracking (github.com) 车牌检测&#xff0…

Docker前后端项目部署

目录 一、搭建项目部署的局域网 二、redis安装 三、MySQL安装 四、若依后端项目搭建 4.1 使用Dockerfile自定义镜像 五、若依前端项目搭建 一、介绍前后端项目 二、搭建项目部署的局域网 搭建net-ry局域网&#xff0c;用于部署若依项目 docker network create net-ry -…

node模块分类

模块 分类 在node种有很多模块&#xff0c;有我们自己写的javascript文件&#xff0c;也会有javascript自带的&#xff0c;还有我们可以下载别人写好的javascript。主要分为三大类。 1. 内置模块 在安装node的时候就自带的模块&#xff0c;比如说http、fs、path等。内置模块一…

如何远程访问电脑文件?

远程访问电脑文件是当今数字化时代中十分常见且实用的技术。它允许我们从任何地方的计算机或移动设备访问和操作我们的电脑中的文件。无论是远程工作、远程学习、远程协作还是方便地获得自己计算机上的重要文件&#xff0c;远程访问电脑文件都为我们提供了巨大的便利。 在远程访…

Netty架构

Netty逻辑架构 Netty 的逻辑处理架构为典型网络分层架构设计&#xff0c;网络通信层、事件调度层、服务编排层。 一、 网络通信层 网络通信层的职责是执行网络 I/O 的操作。它支持多种网络协议和 I/O 模型的连接操作。当网络数据读取到内核缓冲区后&#xff0c;会触发网络事件…

为什么猫咪主食冻干价格相差那么大?性价比高的主食冻干分享

养猫知识的不断普及&#xff0c;让主食冻干喂养逐渐受到铲屎官的青睐。但价格仍是部分铲屎官的顾虑。像我这样的资深猫友&#xff0c;早已开始尝试主食冻干喂养。虽然价格稍高&#xff0c;但其为猫咪带来的实际好处是远超其价格的。 作为一个多猫家庭的铲屎官&#xff0c;纯主食…

贝叶斯树定义与构建的寻行数墨

Title: 贝叶斯树定义与构建的寻行数墨 —— Notes for “The Bayes Tree: An Algorithmic Foundation for Probabilistic Robot Mapping” 文章目录 I. 前言II. 贝叶斯树的定义1. 贝叶斯树的背景2. 贝叶斯树的特点3. 贝叶斯树的定义 III. 贝叶斯树的构建1. 贝叶斯树的构建算法2…

springboot源码解析之Model和Map参数解析

springboot源码解析之Model和Map参数解析 标签:源码:springboot 测试代码 Controller public class HelloController {RequestMapping("/helloModelAndMap")public String helloModelAndMap(HttpServletRequest request, Model model, Map<String, Object> …

鸿蒙文章专题-2021年鸿蒙相关的文章废弃

#原因 至于为什么说2021年我的鸿蒙专栏的文章废弃了&#xff0c;只是说没有了参考意义&#xff0c;是因为鸿蒙4.0以前的版本语言从以Java为主过渡为以ArkTS为主。以前的Java版本的工程已经无法再使用了&#xff0c;后续的开发都必须以ArkTS开发语言为主。 其中而且整个项目结构…

Vue深度教程

一、Vue简介 1.简介 2.快速上手 二、基础 1.创建一个Vue应用 2.模板语法 3.响应式基础 4.计算属性 5.Class与 Style绑定 6.条件渲染 7.列表渲染 8.事件处理 9.表单输入绑定 10.生命周期钩子 11.侦听器 12.模板引用 13.组件基础 三、深入组件 1.组件注册 2.Props 3.组件事件 …

vue ui Starting GUI 图形化配置web新项目

前言&#xff1a;在vue框架里面&#xff0c; 以往大家都是习惯用命令行 vue create 、vue init webpack创建新前端项目&#xff0c;而vue ui是一个可视化的图形界面&#xff0c;对于新手来说更加友好了&#xff0c;不但可以创建、管理、还可以更新vue项目&#xff0c;也可以下载…

实现swiper 3d 轮播效果

先上个效果图&#xff0c;代码可以直接拿~ 安装swiper和vue-awesome-swiper 因为项目用的是nuxt2&#xff0c;所以考虑到swiper的兼容问题&#xff0c;选择的是"swiper": “^5.2.0” 首先是安装swiper和vue-awesome-swiper&#xff0c;并指定版本 npm install s…

在 SpringBoot3 中使用 Mybatis-Plus 报错

在 SpringBoot3 中使用 Mybatis-Plus 报错 Property ‘sqlSessionFactory’ or ‘sqlSessionTemplate’ are required Caused by: java.lang.IllegalArgumentException: Property sqlSessionFactory or sqlSessionTemplate are requiredat org.springframework.util.Assert.no…

C语言如何设置随机数

本期介绍&#x1f356; 主要介绍&#xff1a;在C语言中如何创建一个随机数。 文章目录 1. rand函数2. srand函数3. time函数4. 设置随机数的范围 1. rand函数 想要生成随机数&#xff0c;就需要用到C语言提供的一个库函数叫rand&#xff0c;这个函数可以生成0~32767范围内的随机…

Ubuntu/Linux系统下Redis的基本操作命令

版本查询 redis-server --version # 或者redis-server -v 如上图所示&#xff0c;redis-server的版本为6.0.9,证明redis已经安装完成。 启动Redis服务 启动命令如下&#xff1a; redis-server启动成功如下所示&#xff1a; 启动过程中遇到如下问题时&#xff0c;杀死指定端…

可调恒定电流稳压器NSI50150ADT4G车规级LED驱动器 提供专业的汽车级照明解决方案

NSI50150ADT4G产品概述&#xff1a; NSI50150ADT4G可调恒定电流稳压器 (CCR) &#xff0c;是一款简单、经济和耐用的器件&#xff0c;适用于为 LED 中的调节电流提供成本高效的方案&#xff08;与恒定电流二极管 CCD 类似&#xff09;。该 (CCR) 基于自偏置晶体管 (SBT) 技术&…
最新文章