线程安全与互斥锁(访问控制)

news2024/12/23 3:19:47

线程安全问题

因为多个线程是共享地址空间的,也就是很多资源都是共享的。

  • 优点:通信方便
  • 缺点:缺乏访问控制

因为一个线程的操作问题,给其他线程造成了不可控,或者引起崩溃,异常,逻辑不正确等这种现象:线程安全。

image-20230530225440030

创建一个函数没有线程安全问题的话,尽量不要使用全局,stl,malloc,new等会在全局内有效的数据。

使用的话,需要访问控制

线程有自己的独立栈结构。
线程崩溃的影响一定是有限的,因为线程在进程内部,而进程具有独立性。

访问控制之一 —— 互斥

image-20230531165606851

由于线程安全问题,需要引入访问控制:互斥、同步。

几个概念。 临界资源、临界区、互斥、原子性、同步

image-20230531171641321

通过下面这个抢票的例子,发现线程安全问题

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

//抢票逻辑,1000张票,5线程同时抢

int tickes = 1000;

void *ThreadRoutine(void *args){
    // string tname = (char*)args;
    int id = *(int*)args;
    delete (int*)args;

    while(true){
        //临界区
        if(tickes > 0){
            //抢票
            usleep(10000); //usleep 微秒  1s = 1000ms 1ms = 1000us
            cout << "我是[" << id << "]我要抢的票是:" << tickes << endl;
            tickes--;
        }
        else{
            //没有票
            break;
        }
        // cout << tname << "is running ... " << endl;
        // sleep(1);
    }

}
int main(){
    pthread_t tid[5];
    for(int i = 0; i < 5; i++){
        int *id = new int(i); 
        pthread_create(tid+i, nullptr, ThreadRoutine, id); //i值可能会被主线程修改,所以此处用在堆区新建的id
        // pthread_create(tid+i, nullptr, ThreadRun, (void *)"thread 1");
    }

    for(int i = 0; i < 5; i++)
        pthread_join(tid[i], nullptr); //等待

    return 0;
}

image-20230531190342903

tickets--在此处并不安全

image-20230531202114714

因此,需要对临界区进行加锁。

mutex 互斥锁

image-20230531203848844

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

//抢票逻辑,1000张票,5线程同时抢
//对临界区进行加锁
class Ticket{
private:
    int tickets;
    pthread_mutex_t mtx;
public:
    Ticket():tickets(1000){
        pthread_mutex_init(&mtx, nullptr);
    }

    bool GetTicket(){
        pthread_mutex_lock(&mtx);
        //执行这部分代码的执行流就是互斥的,串行执行的
        if(tickets > 0){
            //抢票
            usleep(1000); //usleep 微秒  1s = 1000ms 1ms = 1000us
            cout << "我是[" << pthread_self() << "]我要抢的票是:" << tickets << endl;
            tickets--; 
        }
        else{
            cout << "票已经被抢空了" << endl;
            return false;
        }
        pthread_mutex_unlock(&mtx);
        return true;
    }
    ~Ticket(){
        pthread_mutex_destroy(&mtx);
    }
};


void *ThreadRoutine(void *args){
    // string tname = (char*)args;
    // int id = *(int*)args;
    // delete (int*)args;

    Ticket *t = (Ticket*)args;

    while(true){
        if(!t->GetTicket()) break; //抢票失败,退出
    }

}
int main(){
    Ticket *t = new Ticket();

    pthread_t tid[5];
    for(int i = 0; i < 5; i++){
        // int *id = new int(i); 
        pthread_create(tid+i, nullptr, ThreadRoutine, (void*)t); 
        // pthread_create(tid+i, nullptr, ThreadRun, (void *)"thread 1");
    }

    for(int i = 0; i < 5; i++)
        pthread_join(tid[i], nullptr); //等待

    return 0;
}

运行结果:

image-20230531211212953

因为使用了互斥锁,线程之间不会造成访问干扰和重入问题。

除了使用原生线程库里的锁,也可使用C++提供的库内的锁,包含在头文件#include <mutex>内,修改代码如下

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

//抢票逻辑,1000张票,5线程同时抢
//对临界区进行加锁
class Ticket{
private:
    int tickets;
    pthread_mutex_t mtx;  //原生线程库,系统级别
    mutex mytex;  //C++语言级别
public:
    Ticket():tickets(1000){
        pthread_mutex_init(&mtx, nullptr);
    }

    bool GetTicket(){
        // pthread_mutex_lock(&mtx);
        mytex.lock();
        //执行这部分代码的执行流就是互斥的,串行执行的
        if(tickets > 0){
            //抢票
            usleep(1000); //usleep 微秒  1s = 1000ms 1ms = 1000us
            cout << "我是[" << pthread_self() << "]我要抢的票是:" << tickets << endl;
            tickets--; 
        }
        else{
            cout << "票已经被抢空了" << endl;
            return false;
        }
        // pthread_mutex_unlock(&mtx);
        mytex.unlock();
        return true;
    }
    ~Ticket(){
        pthread_mutex_destroy(&mtx);
    }
};


void *ThreadRoutine(void *args){
    // string tname = (char*)args;
    // int id = *(int*)args;
    // delete (int*)args;

    Ticket *t = (Ticket*)args;

    while(true){
        if(!t->GetTicket()) break; //抢票失败,退出
    }

}
int main(){
    Ticket *t = new Ticket();

    pthread_t tid[5];
    for(int i = 0; i < 5; i++){
        // int *id = new int(i); 
        pthread_create(tid+i, nullptr, ThreadRoutine, (void*)t); //i值可能会被主线程修改,所以此处用在堆区新建的id
        // pthread_create(tid+i, nullptr, ThreadRun, (void *)"thread 1");
    }

    for(int i = 0; i < 5; i++)
        pthread_join(tid[i], nullptr); //等待

    return 0;
}

运行效果与上述相同。

也可定义静态锁,如下,静态锁的使用。

class Ticket{
private:
    int tickets;
    // pthread_mutex_t mtx;  //原生线程库
    // mutex mytex;  //C++语言级别
public:
    Ticket():tickets(1000){
        // pthread_mutex_init(&mtx, nullptr);
    }

    bool GetTicket(){
        static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; //定义一把静态锁
        //锁本身也是临界资源,如何保证其是安全的?
        //lock、unlock是原子性的
        //一行汇编,即为原子
        pthread_mutex_lock(&mtx);
        // mytex.lock();
        //执行这部分代码的执行流就是互斥的,串行执行的
        if(tickets > 0){
            //抢票
            usleep(1000); //usleep 微秒  1s = 1000ms 1ms = 1000us
            cout << "我是[" << pthread_self() << "]我要抢的票是:" << tickets << endl;
            tickets--; 
        }
        else{
            cout << "票已经被抢空了" << endl;
            return false;
        }
        pthread_mutex_unlock(&mtx);
        // mytex.unlock();
        return true;
    }
    ~Ticket(){
        // pthread_mutex_destroy(&mtx);
    }
};

访问临界资源的时候,需要先访问mtx,前提是所有线程必须得看到它。所以,锁本身也是临界资源。lockunlock原子的,故而,保证了锁本身是安全的。

通过接下来的内容了解互斥锁(互斥量)的原理。

互斥锁实现原理探究

一行汇编,即是原子的。

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

下面是lock以及unlock底层实现的伪代码:

image-20230531223914986

死锁

  • 一个线程造成死锁:在一个加锁程序中,又进行一次加锁操作,并且这个锁不是可重入锁,就会导致死锁。内部锁在等外部锁释放,外部锁需要走完整个流程才能释放锁。

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

死锁四个必要条件:互斥条件、请求与保持条件、不剥夺条件、循环等待条件

避免死锁

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

避免死锁算法

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

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

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

相关文章

基于Java实现农产品交易平台的设计与实现_kaic

【摘要】农业是我国国民经济的重要组成部分&#xff0c;随着信息化的普及&#xff0c;4G网络、光纤以及5G网络也日益完善&#xff0c;农业信息化的发展成为了必然。同时&#xff0c;由于本年疫情原因&#xff0c;导致农作物积压销售&#xff0c;甚至腐烂造成不必要的浪费&#…

chatgpt赋能python:Python信息抽取——帮您更好地利用数据

Python信息抽取——帮您更好地利用数据 什么是Python信息抽取&#xff1f; Python信息抽取是指利用Python编写程序&#xff0c;从大量的非结构化数据中提取有用的信息的技术。这些数据可以是网页、文本文件、PDF等各种格式&#xff0c;而Python信息抽取则可以帮助您快速、准确…

分享一个case when then when then end的sql语句编写用法

目录 写作背景我和若依的前一部分相同思路我的后续解决思路&#xff08;不建议&#xff09;若依后续解决思路&#xff08;建议&#xff09;若依后续解决思路举例 写作背景 平时我用case when then else end的机会也不多&#xff0c;之前用它来做对select结果进行计算&#xff…

chatgpt赋能python:Python代码20行,助力千万SEO从业者快速实现网站分析

Python代码20行&#xff0c;助力千万SEO从业者快速实现网站分析 SEO是现代数字营销的核心战略之一。对于千万从业者而言&#xff0c;网站分析是SEO实践的重要一环。而Python作为一门高效、简洁的编程语言&#xff0c;其丰富的第三方库和易学易用的语法使其成为网站分析的强大工…

【MySQL】从0到1打开数据库管理

目录 前言&#xff1a; 一.认识MySQL 二.安装MySQL数据库 三、启动和停止MySQL服务 3.1启动服务的两种方式 3.2停止服务的两种方式 四.链接客户端 4.1使用自带的命令行窗口 4.2使用系统自带的命令窗口 五.MySQL是存储数据的模型 六.SQL语言 结尾&#xff1a; 前言&a…

设计二:51单片机外部中断控制

目录 一、设计内容 二、中断相关知识 1、51单片机中断源 2、中断系统特殊功能寄存器 3、中断函数与函数调用区别 三、仿真原理图 四、程序设计 五、仿真结果 六、思考题 作者有话说 一、设计内容 本次设计使用2个按键&#xff0c;在无按键按下时&#xff0c;最下面一…

chatgpt赋能python:Python代码50行:如何使用Python进行SEO分析

Python代码50行&#xff1a;如何使用Python进行SEO分析 随着互联网的发展&#xff0c;Search Engine Optimization&#xff08;SEO&#xff09;已经成为企业网络营销策略中至关重要的一环。而Python作为一种全能的编程语言&#xff0c;已经逐渐成为许多SEO工程师的首选工具。本…

chatgpt赋能python:Python修改List的教程

Python修改List的教程 当涉及到Python编程时&#xff0c;对于处理和管理数据&#xff0c;List是一个非常常见和有用的数据结构。像大多数数据结构一样&#xff0c;有时需要对List进行修改&#xff0c;以便更好地满足程序的需求。因此&#xff0c;本文将向您展示如何使用Python…

数据结构与算法09:二叉树

目录 【树】 【二叉树】 二叉树的遍历 Go代码实现 二叉树的复杂度分析 【二叉搜索树】 Go代码实现 【每日一练&#xff1a;移除元素】 【树】 什么是树&#xff1f;这个不用解释了吧&#xff0c;马路两边种的都是树。数据结构里面的“树”和现实生活中的树类似&#…

chatgpt赋能python:Python会动的图形:如何让你的网站活力四射

Python会动的图形&#xff1a;如何让你的网站活力四射 如果你想让你的网站更具生命力、吸引力和互动性&#xff0c;一种非常有效的方式是使用动态图形。而Python有许多强大的库可以帮助你实现这一目标。在本篇文章中&#xff0c;我们将讨论Python会动的图形的好处、如何实现和…

过滤器JavaWeb:Filter与拦截器Spring:Intercepter

过滤器与拦截器若同时存在&#xff0c;先执行过滤器的放行前&#xff0c;再执行整个拦截器&#xff0c;最后再执行过滤器的放行后 过滤器会拦截所有资源&#xff08;包括静态资源&#xff09;&#xff0c;拦截器只会拦截Spring环境的资源 Filter的使用 1、创建一个类implement…

深蓝学院C++基础笔记 第 1 章 C++初探

第 1 章 C初探 1&#xff0e;从Hello World 谈起 Hello World: #include <iostream> int mian() { std::cout << "Hello World!" << std::endl; }函数: 一段能被反复调用的代码&#xff0c;可以接收输入&#xff0c;进行处理并(或)产生输出-返回…

Postgres vs MySQL

主要区别及示例 简而言之&#xff0c;Postgres 和 MySQL 之间的主要区别实际上归结为主索引和辅助索引的实现方式以及数据的存储和更新方式。 让我们进一步探讨这个问题。 但首先... 基础知识 索引是一种数据结构&#xff08;主要是 B 树&#xff09;&#xff0c;允许通过…

DAY01_MySQL基础数据类型navicat使用DDL\DML\DQL语句练习

目录 1 数据库相关概念1.1 数据库1.2 数据库管理系统1.3 常见的数据库管理系统1.4 SQL 2 MySQL2.1 MySQL安装2.1.1 安装步骤 2.2 MySQL配置2.2.1 添加环境变量2.2.2 MySQL登录2.2.3 退出MySQL 2.3 MySQL数据模型2.4 MySQL目录结构2.5 MySQL一些命令2.5.1 修改默认账户密码2.5.2…

Linux 计划任务(at与crontab)

一次性计划任务 at Linux 中的【 at 】 命令是用来创建一次性计划任务的&#xff0c; at 命令有一个服务 atd 以后台的模式运行&#xff0c;通过检查当前的时间来决定是 否运行 " 计划 " &#xff0c;默认情况下&#xff0c; atd 服务每 60 秒检 查一次&#x…

【Web服务应用】Nginx服务

Nginx服务 一、Nginx概述1.1Nginx特点1.2Nginx作用1.3Nginx与Apache的差异 二、Nginx进程模型三、编译安装Nginx3.1Nginx服务的检查、启动、停止&#xff0c;重载3.2平滑升级3.3把nginx进程加入到系统服务当中 四、Nginx服务的主配置文件nginx.conf4.1补充什么是IO多路复用4.2根…

R语言:移动平均计算及绘图

问题描述 现在有一个分日期记录DAU的数据&#xff0c;现在需要绘制其360,180,90,30,7日移动平均值&#xff0c;来观测消除了波动干扰的DAU趋势 (实际移动在股价趋势图上非常常见) 原始数据格式如下&#xff1a; day &#xff08;character&#xff09; dau &#xff08;int…

Docker+Jenkins+Gitee自动化部署maven单模块项目

1.简介 各位看官老爷&#xff0c;本文为Jenkins实战&#xff0c;注重实际过程&#xff0c;阅读完会有以下收获&#xff1a; 了解如何使用Docker安装Jenkins了解如何使用Jenkins部署maven项目了解如何使用JenkinsGitee实现自动化部署 2.Jenkins介绍 相信&#xff0c;正在读这…

2023年上半年软件设计师上午真题及答案解析

1.计算机中&#xff0c;系统总线用于( )连接 A.接口和外设 B.运算器&#xff0c;控制器和寄存器 C.主存、外设部件 D.DMA控制器和中断控制器 2.在由高速缓存、主存和硬盘构成的三级存储体系中&#xff0c;CPU执行指令时需要读取数据&#xff0c;那么DMA控制…

深入理解Linux虚拟内存管理(一)

系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核&#xff08;一&#xff09; 深入理解 Linux 内核&#xff08;二&#xff09; Linux 设备驱动程序&#xff08;一&#xff09; Linux 设备驱动程序&#xff08;二&#xff09; Linux 设备驱动程序&#xff08;三&#xf…