Linux锁的使用

news2024/10/6 8:34:39

一、临界资源与临界区

多线程会共享例如全局变量等资源,我们把会被多个执行流访问的资源称为临界资源,我们是通过代码访问临界资源的,而我们访问临界资源的那部分代码称为临界区。

实现一个抢票系统

只有一个线程抢票时

#include <iostream>
#include <vector>
#include <unistd.h>

#include "Thread.hpp"


int num = 10000; 

std::string GetThreadName()
{
    static int num = 1;
    char name[64];
    snprintf(name, sizeof(name), "thread-%d", num++);
    return name;
}

void Ticket(std::string name)
{
    while(true)
    {
        if(num > 0)
        {
            usleep(1000);
            printf("%s get ticket: %d\n", name.c_str(), num);
            num--;
        }
        else
        {
            break;
        }
    }
}

int main()
{
    std::string name1 = GetThreadName();
    Thread<std::string> t1(name1, Ticket, name1);
    
    t1.Start();
    t1.Join();
    
    return 0;
}

正常输出,最终票数为0时退出。

但是当我们启动多个线程同时抢票时,num就是临界资源,使用num的那部分代码就是临界区

#include <iostream>
#include <vector>
#include <unistd.h>

#include "Thread.hpp"


int num = 10000; 

std::string GetThreadName()
{
    static int num = 1;
    char name[64];
    snprintf(name, sizeof(name), "thread-%d", num++);
    return name;
}

void Ticket(std::string name)
{
    while(true)
    {
        if(num > 0)
        {
            usleep(1000);
            printf("%s get ticket: %d\n", name.c_str(), num);
            num--;
        }
        else
        {
            break;
        }
    }
}

int main()
{
    std::string name1 = GetThreadName();
    Thread<std::string> t1(name1, Ticket, name1);
    std::string name2 = GetThreadName();
    Thread<std::string> t2(name2, Ticket, name2);
    std::string name3 = GetThreadName();
    Thread<std::string> t3(name3, Ticket, name3);
    std::string name4 = GetThreadName();
    Thread<std::string> t4(name4, Ticket, name4);

    t1.Start();
    t2.Start();
    t3.Start();
    t4.Start();

    t1.Join();
    t2.Join();
    t3.Join();
    t4.Join();
    
    return 0;
}

可以看到出现了0和负数的票数,这是因为当票数只剩1时,有多个执行流在同一时间通过了if判断,使得能继续进行减票操作。

vs下自减操作的反汇编,分为三步:先从内存拿数据,再把数据减1,最后把数据拷贝到内存

多个执行流同时访问临界资源例如自减操作,由于--操作不是原子性的(我们认为一条汇编指令是原子性的,是不会被中断的。但--操作转为汇编指令后,需要多条指令才能完成),当--操作执行到一半切换到其他线程会导致数据不一致的问题。这种情况下需要通过锁把临界区保护起来,每次只让一个执行流访问临界资源,避免数据不一致问题。

互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。

原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

二、使用锁的方法

1.创建锁

如果定义一个全局的锁,直接使用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER用宏初始化。

如果定义一个局部锁,要使用pthread_mutex_init方法创建,参数attr设为nullptr

2.加锁解锁

使用pthread_mutex_lock加锁,传递锁的地址,

解锁用pthread_mutex_unlock

当我们使用锁后,就能保证每次只有一个执行流能访问临界资源。

#include <iostream>
#include <vector>
#include <unistd.h>

#include "Thread.hpp"


int num = 10000;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //定义一个全局锁

std::string GetThreadName()
{
    static int num = 1;
    char name[64];
    snprintf(name, sizeof(name), "thread-%d", num++);
    return name;
}

void Ticket(std::string name)
{
    while(true)
    {
        pthread_mutex_lock(&mutex); //加锁
        if(num > 0)
        {
            usleep(1000);
            printf("%s get ticket: %d\n", name.c_str(), num);
            num--;
            pthread_mutex_unlock(&mutex); //解锁
        }
        else
        {
            pthread_mutex_unlock(&mutex); //解锁
            break;
        }
    }
}

int main()
{
    std::string name1 = GetThreadName();
    Thread<std::string> t1(name1, Ticket, name1);
    std::string name2 = GetThreadName();
    Thread<std::string> t2(name2, Ticket, name2);
    std::string name3 = GetThreadName();
    Thread<std::string> t3(name3, Ticket, name3);
    std::string name4 = GetThreadName();
    Thread<std::string> t4(name4, Ticket, name4);

    t1.Start();
    t2.Start();
    t3.Start();
    t4.Start();

    t1.Join();
    t2.Join();
    t3.Join();
    t4.Join();

    return 0;
}

结果正常,但是速度慢了很多,因为要不断申请锁和释放锁

加锁解锁的过程是安全的

三、可重入和线程安全

1.概念

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

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

2.常见的线程不安全的情况

1.不保护共享变量的函数

2.函数状态随着被调用,状态发生变化的函数

3.返回指向静态变量指针的函数

4.调用线程不安全函数的函数

3.常见的线程安全的情况

1.调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的

2.调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构

3.可重入函数体内使用了静态的数据结构

4.常见可重入的情况

1.不使用全局变量或静态变量

2.不使用用malloc或者new开辟出的空间

3.不调用不可重入函数不返回静态或全局数据,所有数据都有函数的调用者提供

4.使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

5.可重入与线程安全联系

1.函数是可重入的,那就是线程安全的

2.函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题

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

6.可重入与线程安全区别

1.可重入函数是线程安全函数的一种

2.线程安全不一定是可重入的,而可重入函数则一定是线程安全的。

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

四、死锁

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

1.死锁四个必要条件

1.互斥条件:一个资源每次只能被一个执行流使用

2.请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放

3.不剥夺条件: 一个执行流已获得的资源,在末使用完之前,不能强行剥夺

4.循环等待条件: 若干执行流之间形成一种头尾相接的循环等待资源的关系

2.避免死锁

1.破坏死锁的四个必要条件

2.加锁顺序一致

3.避免锁未释放的场景

4.资源一次性分配

3.避免死锁算法

1.死锁检测算法

2.银行家算法

一个锁会造成死锁吗?

答案是会的,当一个线程申请完一个锁,访问完临界资源后,接下来该释放锁了,但是代码却写成了加锁,这就会导致死锁问题。

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

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

相关文章

雷达学习之多普勒频率

一、多普勒频率如何产生&#xff1f; 雷达的原理是发射一些无线电脉冲来探测目标&#xff0c;并通过回波的延时来计算目标与雷达的距离&#xff0c;但当目标为运动物体时&#xff0c;在回波向目标传输的同时&#xff0c;目标也会远离或接近回波&#xff0c;所以会导致回波信号…

CDN加速原理那些事

名词解释 CNAME记录&#xff08;CNAME record&#xff09; CNAME即别名( Canonical Name )&#xff1b;可以用来把一个域名解析到另一个域名&#xff0c;当 DNS 系统在查询 CNAME 左面的名称的时候&#xff0c;都会转向 CNAME 右面的名称再进行查询&#xff0c;一直追踪到最后…

vue 中使 date/time/datetime 类型的 input 支持 placeholder 方法

一般在开发时&#xff0c;设置了 date/time/datetime 等类型的 input 属性 placeholder 提示文本时&#xff0c; 发现实际展示中却并不生效&#xff0c;如图&#xff1a; 处理后效果如图&#xff1a; 处理逻辑 判断表单项未设置值时&#xff0c;则设置其伪类样式&#xff0c;文…

Pillow教程11:九宫格切图的实现方法(安排!!!)

---------------Pillow教程集合--------------- Python项目18&#xff1a;使用Pillow模块&#xff0c;随机生成4位数的图片验证码 Python教程93&#xff1a;初识Pillow模块&#xff08;创建Image对象查看属性图片的保存与缩放&#xff09; Pillow教程02&#xff1a;图片的裁…

ctf_show笔记篇(web入门---SSTI)

前言 模板引擎 模板引擎是为了让用户界面以及业务数据分离开才产生的&#xff0c;模板引擎会生成特定的文档&#xff0c;然后通过模板引擎生成前端html代码&#xff0c;然后再获取用户数据再放到渲染函数里渲染&#xff0c;最后将生成的html代码个渲染好的数据结合拿给浏览器呈…

【Git教程】(十)版本库之间的依赖 —— 项目与子模块之间的依赖、与子树之间的依赖 ~

Git教程 版本库之间的依赖 1️⃣ 与子模块之间的依赖2️⃣ 与子树之间的依赖&#x1f33e; 总结 在 Git 中&#xff0c;版本库是发行单位&#xff0c;代表的是一个版本&#xff0c;而分支或标签则只能被创建在版本库这个整体中。如果一个项目中包含了若干个子项目&#xff0c;…

学习赚钱两不误--全自动挂机软件(网心云)

1、简介 程序员的工作环境程序员的工作环境最多的就是网络资源&#xff0c;所以我们工作中有很多的闲置网络资源&#xff0c;不白嫖有点浪费哈。下面就给大家介绍一下免费分享上行带宽赚钱的平台--网心云 优点&#xff1a; &#xff08;1&#xff09;平台大、靠谱、稳定 &a…

四川易点慧电子商务抖音小店可靠购物

在当下这个信息爆炸的时代&#xff0c;电子商务的崛起不仅改变了人们的购物习惯&#xff0c;也催生了众多新兴的电商平台。四川易点慧电子商务抖音小店便是其中的佼佼者&#xff0c;以其独特的魅力和可靠性&#xff0c;赢得了广大消费者的青睐。 一、平台背景实力雄厚 四川易点…

3、最大池化maxinmum pooling

了解有关最大池化特征提取的更多信息。 简介 在第二课中,我们开始讨论卷积神经网络(convnet)的基础如何进行特征提取。我们了解了这个过程中的前两个操作是在带有 relu 激活的 Conv2D 层中进行的。 在这一课中,我们将看一下这个序列中的第三个(也是最后一个)操作:通过…

(一)基于IDEA的JAVA基础12

一维数组 为什么使用数组: 当我们需要存储一系列数据的时候&#xff0c;就需要用到数组&#xff0c;如果不使用数组&#xff0c;我们就要需要一个一个的去声明变量&#xff0c;这样浪费内存空间&#xff0c;同时效率低下。 什么是数组: 数组本身就是一个变量&#xff0c;只…

蓝桥杯-【二分】分巧克力,跳石头

代码及解析: #include<bits/stdc.h> using namespace std; int n,k; const int N100010; int h[N],w[N]; bool check(int d){int num0;for(int i0;i<n;i) num (h[i]/d)*(w[i]/d);if(num>k) return true; //够分else return false; //不够分 } in…

Linux: 工具: tshark 抓到了收方向的ESP明文包?

根据这个描述&#xff0c;看着是正常的&#xff0c; 抓到包之后&#xff0c;可以方便的分析问题&#xff0c;省去在wireshark里解码的问题。 经过调查发现是内核将ESP解开之后&#xff0c;如果是tunnel模式&#xff0c;内核又重新将skb丢给了interface去做处理。这样tshark/tcp…

LightDB24.1 ecpg支持exec sql for :i update(or insert)语法

背景 oracle 在适配过程中&#xff0c;发现pro*c支持exec sql for :i update(or insert)语法&#xff0c;其功能是取代 for(;;) {update(or insert)语法; }其中i决定循环执行的次数&#xff0c;update(or insert)表示循环执行的次数。 我们在oracle环境下测试得到如下经验&a…

diffusion model(十五) : IP-Adapter技术小结

infopaperhttps://arxiv.org/pdf/2308.06721.pdfcodehttps://github.com/tencent-ailab/IP-Adapterorg.Tencent AI Lab个人博客地址http://myhz0606.com/article/ip_adapter 1 Motivation 为了对文生图diffusion model进行特定概念的定制&#xff0c;常用LoRA[1]、textual in…

国内超声波清洗机排名!洗眼镜超声波清洗机推荐

眼镜是我们日常生活中不可或缺的用具&#xff0c;但随着使用时间的增长&#xff0c;眼镜上的灰尘和污垢也会逐渐积累&#xff0c;传统的清洗方法往往难以彻底清洁。为了解决这一难题&#xff0c;超声波清洗机出现了&#xff01;它利用超声波振动原理&#xff0c;可以轻松、快速…

使用Docker部署jar包

vi DockerfileDockerfile内容 FROM java:8 ADD chery5G-admin.jar chery5G-admin.jar ENTRYPOINT ["java","-jar","chery5G-admin.jar"]上传jar包到Dockerfile文件同级目录 使用Dockerfile文件&#xff0c;将jar包制作为镜像 docker build -t…

2024/4/5—力扣—在排序数组中查找元素的第一个和最后一个位置

代码实现&#xff1a; 思路&#xff1a;二分法 方法一&#xff1a;分别查找左右侧边界 /*** Note: The returned array must be malloced, assume caller calls free().*/ int GetTargetFirstPosition(int *nums, int numsSize, int target) {int l 0, r numsSize - 1;while …

JAVA面试八股文之Redis相关

Redis相关 Redis6.0为什么要用多线程&#xff1f;在Redis中存一个list集合怎么实现排序&#xff1f;Redis的5大基本类型的底层原理&#xff1f;缓存穿透&#xff1f;缓存击穿&#xff1f;缓存雪崩&#xff1f;redis做为缓存怎么保持和mysql数据进行同步&#xff1f;&#xff08…

物证管理系统|DW-S404实现物证全生命周期管理

物证管理系统|DW-S404实现物证全生命周期管理 一、项目背景 随着社会的进步和科技的发展&#xff0c;信息化和数字化已经成为各个行业的必然趋势。在众多领域中&#xff0c;物证管理系统逐渐受到广泛的关注和应用。 物证是公安机关处理案件的关键凭证&#xff0c;针对过去物证管…

如何给MySQL数据库的所有表统一加上字段

在开发过程中&#xff0c;有时候慢慢的建了很多数据库表&#xff0c;但是后来发现需要统一增加某些字段的时候&#xff0c;可以通过alter语句 ALTER TABLE 表名 ADD 列名 数据类型;比如我要给t_user表增加gmt_create与gmt_modified字段&#xff0c;用作记录新增记录时间与更新…