深入了解线程锁的使用及锁的本质

news2024/12/24 11:27:17

文章目录

  • 线程锁的本质
    • 局部锁的使用
  • 锁的封装及演示
    • 线程饥饿问题
  • 线程加锁本质
  • 可重入和线程安全
  • 死锁问题

根据前面内容的概述, 上述我们已经知道了在linux下关于线程封装和线程互斥,锁的相关的概念, 下面就来介绍一下关于线程锁的一些其他概念.

线程锁的本质

当这个锁是全局的或者是静态属性时,可以使用PTHREAD_MUTEX_INITIALIZER (initializer 初始化器(初始化列表那样的东西)),这个宏来进行初始化.

局部锁的使用

局部的锁就要使用pthread_mutex_init()创建, pthread_mutex_destroy()来销毁
在这里插入图片描述
在这里插入图片描述

回调函数处:
在这里插入图片描述

锁的封装及演示

这边引入锁的封装, 将线程名称与锁进行封装的一种保护机制(lock guard):.
意义在于: 创建后再程序结束时会自动释放锁,方便使用

LockGuard.hpp定义

#pragma once
#include <iostream>
//不定义锁,默认认为外部会给我们传入锁对象
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock):_lock(lock)//包装加锁功能可以实现启动自定义锁时自定加锁,然后对应的函数功能结束自动解锁(利用构造函数和析构函数的性质实现)
    {}
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {}
    
private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock):_mutex(lock)//_mutex是Mutex的对象,该对象调用对应的方法
    {
        _mutex.Lock();//调用Mutex类的加锁方法
    }
    ~LockGuard ()
    {
        _mutex.Unlock();
    }

private:
    Mutex _mutex;
};

Thread.hpp 对pthread线程的封装实现

#pragma once
#include <iostream>
#include <functional>
#include <pthread.h>
#include <string>

using namespace std;
template<class T>
using func_t = function<void(T)>;//std::function 是C++标准库中的一个模板类,可以包装任何可调用目标(callable target),比如函数、lambda表达式、函数对象(functor)等。
template<class T>
class Thread
{
public:
    Thread(const string &name, func_t<T> func, T data)
        : _name(name), _func(func), _data(data), _tid(0), _isrunning(false)
    {}
    static void *ThreadRoutine(void *args)//子线程入口,接受参数为当前对象的指针
    {
        Thread *t = static_cast<Thread*>(args);//转义为所需要的指针类型,当前的t和this一样,但是不能与库内的this进行重名
        t->_func(t->_data); //当前对象调用参数_func(他是一个function类创建的对象,这个类可以包装任何内容,这边包装函数,_func是这个函数模板创建的对象),接受来自Thread创建时的第三个参数
        //到这边是完成对整个类的包装,模板概念已经结束,具体操作回到main内查看,对应的函数执行结束后,执行exit(0)
        exit(0);
    }
    bool Start()
    {
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, this);//创建线程,加载输出OS给的tid,默认方式创建(不设置分离状态,栈大小等),子线程入口,传入参数给子线程
        if(n == 0)
        {
            _isrunning = true;
            return true;
        }
        else
        {
            return false;
        }
    }
    bool Join()
    {
        if(!_isrunning)
        {
            return true;
        }
        int n = pthread_join(_tid, nullptr);
        if(n == 0)
        {
            _isrunning = false;
            return true;
        }
        else
        {
            return false;
        }
    }
    ~Thread(){}
    bool IsRunning()
    {
        return _isrunning;
    }

private:
    pthread_t _tid;
    string _name;
    func_t<T> _func;
    T _data;
    bool _isrunning;
};

main.cc代码演示

#include "Thread.hpp"
#include <unistd.h>
#include "LockGuard.hpp"

class ThreadData
{
public:
    ThreadData(string name, pthread_mutex_t *pmutex)
        : _name(name), pmutex(pmutex)
    {
    }
    ~ThreadData()
    {
    }

public:
    string _name;
    pthread_mutex_t *pmutex;
};
int numsize = 10000;

string GetThreadName()
{
    static int num = 1;
    return static_cast<string>("Thread-" + to_string(num++));
}
void Print(ThreadData *td)//执行Print方法,参数是来自线程创建函数的第四个参数,这一功能也由线程创建函数实现,功不可没,十分可秒啊
{
    //全局内定义的参数进行--操作,验证线程互斥问题
    while (true)
    {
        {//将临界区进行花括号包裹,代码更加明显
            LockGuard lockguard(td->pmutex);//利用锁保护功能模块进行加锁(启动锁)
            // LockGuard lockguard(&mutex);
            // pthread_mutex_lock(mutex);
            if (numsize > 0)
            {

                usleep(1000);
                std::cout << td->_name << ", the numsize is: " << numsize << std::endl;
                --numsize;
                // pthread_mutex_unlock(mutex);
            }
            else
            {
                // pthread_mutex_unlock(mutex);
                break;
            }
        }//加锁, 解锁功能结束,一个线程访问临界区的操作也结束,意味着后续线程可以访问这个临界区

        //在运行结果时会发现,有时候会出现一个线程把所有numsize都分完了,这是因为线程执行多久是由于时间片决定,当在多线程情况下把所有任务(同一份资源)都做完的情况叫做多线程饥饿问题
    }
}

int main()
{
    pthread_mutex_t mutex; // 创建锁初始化
    pthread_mutex_init(&mutex, nullptr);

    string name1 = GetThreadName();//获取线程名称
    ThreadData *td1 = new ThreadData(name1, &mutex); // 将锁和线程的名字的信息写入ThreadData,便于管理
    Thread<ThreadData *> t1(name1, Print, td1);//为线程创建进行加载对应信息

    string name2 = GetThreadName();
    ThreadData *td2 = new ThreadData(name2, &mutex);
    Thread<ThreadData *> t2(name2, Print, td2);

    string name3 = GetThreadName();
    ThreadData *td3 = new ThreadData(name3, &mutex);
    Thread<ThreadData *> t3(name3, Print, td3);

    string name4 = GetThreadName();
    ThreadData *td4 = new ThreadData(name4, &mutex);
    Thread<ThreadData *> t4(name4, Print, td4);

    string name5 = GetThreadName();
    ThreadData *td5 = new ThreadData(name5, &mutex);
    Thread<ThreadData *> t5(name5, Print, td5);

    t1.Start();//线程启动
    t2.Start();
    t3.Start();
    t4.Start();
    t5.Start();

    t1.Join();//线程等待
    t2.Join();
    t3.Join();
    t4.Join();
    t5.Join();

    pthread_mutex_destroy(&mutex); // 消除锁

    return 0;
}

基于上篇文章定义对main内的一些修改:
在这里插入图片描述

线程饥饿问题

再多线程创建后
在运行结果时会发现,有时候会出现一个线程把所有numsize都分完了,这是因为线程执行多久是由于时间片决定,当在多线程情况下把所有任务(同一份资源)都做完的情况叫做多线程饥饿问题.

要解决饥饿问题要让线程在执行时,预备一定的顺序性–这就是线程同步(下章见晓)

线程加锁本质

原子性问题在软硬件层面的体现
软件方面

线程能被调度是因为OS以一种非常快的方式来受理时钟中断,这时就会执行调度进程

硬件方面

把中断关掉,这时只执行进程,OS不会继续执行,这时不会进行调度

大部分的体系结构(像X86,AMD芯片中)会提供swap和exchange汇编级的指令,作用是把寄存器的内容和内存单元的内容进行数据交换

1.exchange eax mem_addr //将eax 和 mem_addr的内容进行交换
直接进行交换,这一个操作是原子性的
2.什么是一把锁?在代码中是创建一个变量,首先把他想象成一个变量struct {int num = 1;}
利用伪代码进行理解:在这里插入图片描述
在这里插入图片描述

关于加锁的原则: 谁加锁,谁解锁.

可重入和线程安全

可重入VS线程安全:

可重入还是不可重入描述的是函数的问题,跟线程无关,他描述的是函数的特点,无褒贬之分,函数大部分都是不可重入

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

eg:线程访问不可重入函数是线程不安全的情况之一

线程安全的操作:

对于一个全局的变量,在开始改变完他的值之后在退出这个函数之前将值恢复成开始的值,这样来变相的达到线程安全的操作,这只是其中一个例子

可重入与线程安全是二义性

函数可重入意味着当线程进入这个函数是线程安全的
反之,当这个函数不可重入,那么就是线程不安全的

死锁问题

问题解释: 处于一组进程中的各个线程不会释放资源,但因为相互申请被其他进程所占据不会释放资源而处于一种永久等待的状态(多个执行流在一段时间内因为相互牵制不会向后推进)

死锁产生的四个必要条件:

互斥条件:一个资源只能被一个执行流使用(产生死锁的根本原因)
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放(把自己的锁拿的紧紧地,还伸手向别人要锁)
不剥夺条件:一个执行流已获得的资源,在未使用之前,不能被强行剥夺(锁2不能解锁1)
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源关系(互相申请对方的的锁的问题,形成了申请的循环)

当上述四个条件都成立才会产生死锁

如何避免死锁呢?

避免死锁:不用锁
但是为了保护共享资源, 提出来的使用锁
核心原理:
1.破坏4个必要条件中的一个或者多个
2.建议, 按照同样的次序进行申请锁的操作(加锁循序尽量保持一致)
尽量把锁的资源,按照申请的资源一次给申请线程了,这样不易出现错误(目前用不到)
3.避免锁未被释放的场景发生
4.资源一次性分配

注:

一个线程也能实现死锁:
比如:不下心把解锁写成了加锁,这个时候就会出错
这个时候是自己阻塞自己

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

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

相关文章

5.更多

发现一个项目与 MkDocs 类似的项目 PyMdown 拓展文档 &#xff0c;等待探索。 1.排版模仿 以下网站使用 MkDocs 构建 Material for MkDocs 的美化 - Charles Les Notebook (charleschile.com) Documentation - Home Assistant (home-assistant.io) Godot Docs – master bra…

什么是数据同步服务RSYNC?

大家好呀&#xff01;这里是码农后端。今天来介绍一下数据同步服务RSYNC&#xff0c;作为Linux/Unix系统中远程或本地复制同步&#xff08;复制&#xff09;文件和目录最常用的命令&#xff0c;相比于scp命令&#xff0c;其具有增量备份、数据同步时保持文件的原有属性等优点。…

[激光原理与应用-102]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 6 - 激光焊接系统的组成

目录 一、激光焊接系统的组成概述 1.1、核心部件 1.2、焊接执行部件 1.3、辅助系统 1.4、控制系统 1.5、其他辅助设备 二、激光器 2.1 按出光类型分 1. 脉冲激光器 2. 连续激光器 3. 准连续激光器&#xff08;QCW&#xff09; 4. 其他常见激光器 5. 应用领域 2.2…

CentOS6禁止锁屏

在电源中设置后还是会锁屏, 原因是有屏幕保护程序 电源管理都 “从不” 一些AI的回答 在CentOS 6系统中&#xff0c;如果你想要禁用锁屏功能&#xff0c;可以编辑/etc/kbd/config文件。这个文件通常包含了键盘相关的设置&#xff0c;包括密码策略和屏幕锁定选项。 首先打开终…

昇思14天

ResNet50图像分类 1. ResNet50图像分类概述 ResNet50是一种用于图像分类的深度卷积神经网络。图像分类是计算机视觉的基本应用&#xff0c;属于有监督学习范畴。ResNet50通过引入残差结构&#xff0c;解决了深层网络中的退化问题&#xff0c;使得可以训练非常深的网络。 2. …

List、Map、Set 接口在Java中的存取元素特点

List、Map、Set 接口在Java中的存取元素特点 1、List 接口2、Map 接口3、Set 接口4、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java中&#xff0c;List、Map和Set是三个最常用的集合接口。它们各自有不同的特点和用途&#xff…

实践致知第12享:如何新建一个Word并设置格式

一、背景需求 小姑电话说&#xff1a;要新建一个Word文档&#xff0c;并将每段的首行设置空2格。 二、解决方案 1、在电脑桌面上空白地方&#xff0c;点击鼠标右键&#xff0c;在下拉的功能框中选择“DOC文档”或“DOCX文档”都可以&#xff0c;如下图所示。 之后&#xff0…

【密码学】分组密码概述

一、分组密码的定义 分组密码和流密码都是对称密码体制。 流密码&#xff1a;是将明文视为连续的比特流&#xff0c;对每个比特或字节进行实时加密&#xff0c;而不将其分割成固定的块。流密码适用于加密实时数据流&#xff0c;如网络通信。分组密码&#xff1a;是将明文数据…

D2D用户的功率优化算法研究

D2D通信技术是指两个对等的用户节点之间直接进行通信的一种通信方式。在由D2D通信用户组成的分布式网络中&#xff0c;每个用户节点都能发送和接收信号&#xff0c;并具有自动路由(转发消息)的功能。网络的参与者共享它们所拥有的一部分硬件资源&#xff0c;包括信息处理、存储…

【深度学习】第5章——卷积神经网络(CNN)

一、卷积神经网络 1.定义 卷积神经网络&#xff08;Convolutional Neural Network, CNN&#xff09;是一种专门用于处理具有网格状拓扑结构数据的深度学习模型&#xff0c;特别适用于图像和视频处理。CNN 通过局部连接和权重共享机制&#xff0c;有效地减少了参数数量&#x…

Prometheus 二进制文件与操作系统或架构不兼容

目录 检查架构 下载正确的二进制文件 验证文件类型 权限问题 文件损坏 “可执行文件格式错误”表明你试图执行的二进制文件与操作系统或架构不兼容。 检查架构 确保你下载的二进制文件与系统的架构相匹配&#xff08;例如&#xff0c;x86_64, arm64 等&#xff09;。 可…

LabVIEW光谱测试系统

在现代光通信系统中&#xff0c;光谱分析是不可或缺的工具。开发了一种基于LabVIEW的高分辨率光谱测试系统&#xff0c;通过对可调谐激光器、可编程光滤波器和数据采集系统的控制&#xff0c;实现了高效、高精度的光谱测量。 项目背景 随着光通信技术的迅速发展&#xff0c;对…

20240710 每日AI必读资讯

&#x1f916;微软&#xff1a;不会像 OpenAI 一样阻止中国访问 AI 模型 - OpenAI 将于周二&#xff08;7 月 9 日&#xff09;开始阻止中国用户访问其 API。 - 微软发言人表示&#xff1a;Azure OpenAI API服务在中国的提供方式没有变化。 - 公司仍然通过部署在中国以外地区…

Mysql练习题目【7月10日更新】

七、Mysql练习题目 https://zhuanlan.zhihu.com/p/38354000 1. 创建表 创建学生表 mysql> create table if not exists student(-> student_id varchar(255) not null,-> student_name varchar(255) not null,-> birthday date not null,-> gender varchar(…

思考:Java内存模型和硬件内存模型

前言 前一阵在看volatile的原理&#xff0c;看到内存屏障和缓存一致性&#xff0c;发现再往底层挖就挖到了硬件和Java内存模型。这一块是自己似懂非懂的知识区&#xff0c;我一般称之为知识混沌区。因此整理这一篇文章。 什么是内存模型&#xff08;Memory Model&#xff09;…

46、lvs集群- 博客

1、lvs集群&#xff1a; lvs&#xff1a;linux virtual server----章文嵩发起的开源项目&#xff0c;阿里。linux的内核层面实现负载均衡的软件。 主要作用&#xff1a;将多个后端服务器组成一个高可用&#xff0c;高性能的服务器集群&#xff0c;通过负载均衡的算法将客户端的…

前端javascript中的排序算法之插入排序

插入排序&#xff08;Selection Sort&#xff09;基本思想&#xff1a; 插入排序每次排一个数组项&#xff0c;以此方式构建最后的排序数组。假定第一项已经排序了&#xff0c;接着&#xff0c; 它和第二项进行比较&#xff0c;第二项是应该待在原位还是插到第一项之前呢&#…

查看oracle ojdbc所支持的JDBC驱动版本

oracle jcbc驱动的下载地址参考&#xff1a;JDBC and UCP Downloads page 其实上文中对ojdbc所支持的JDBC驱动版本已经有说明了&#xff0c;不过&#xff0c;因为oracle的驱动包很多时间&#xff0c;都是在公司内部私服里上传维护的&#xff0c;上传的时候&#xff0c;可能又没…

第4章 课程发布:模块需求分析,课程预览(模板引擎 静态页面),课程审核,课程发布(分布式事务,页面静态化:熔断降级),课程搜索(es索引)

1 模块需求分析 1.1 模块介绍 课程信息编辑完毕即可发布课程&#xff0c;发布课程相当于一个确认操作&#xff0c;课程发布后学习者在网站可以搜索到课程&#xff0c;然后查看课程的详细信息&#xff0c;进一步选课、支付、在线学习。 下边是课程编辑与发布的整体流程&#…

Java中的 this 关键字是什么意思? this() 又是什么?

目录 问题问题一&#xff1a;什么是this关键字?问题二&#xff1a;什么是this()&#xff1f; 问题 问题一&#xff1a;什么是this关键字? 定义&#xff1a;this 代表当前对象。这个定义比较抽象&#xff0c;举例来回答。 思考一个问题&#xff1a;如果没有 this 会怎样&…