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

文章目录

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

根据前面内容的概述, 上述我们已经知道了在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.rhkb.cn/news/373250.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

6800和8080单片机读写时序和液晶屏接口

前言&#xff1a; 随着单片机发展&#xff0c;集成度越来越高&#xff0c;因此目前单片机较少使用RD和WR信号操作外设&#xff0c;因此很多时候&#xff0c;变成了6800和8080单片机读写液晶屏了。早期的读写本质上是对一个地址进行即时的操作&#xff0c;现在可能是等数据送到…

打开excel时弹出stdole32.tlb

问题描述 打开excel时弹出stdole32.tlb 如下图&#xff1a; 解决方法 打开 Microsoft Excel 并收到关于 stdole32.tlb 的错误提示时&#xff0c;通常意味着与 Excel 相关的某个组件或类型库可能已损坏或不兼容。 stdole32.tlb 是一个用于存储自动化对象定义的类型库&#x…

PowerShell install 一键部署mysql 9.0.0

mysql 前言 MySQL 是一个基于 SQL(Structured Query Language)的数据库系统,SQL 是一种用于访问和管理数据库的标准语言。MySQL 以其高性能、稳定性和易用性而闻名,它被广泛应用于各种场景,包括: Web 应用程序:许多动态网站和内容管理系统(如 WordPress)使用 MySQL 存…

生产环境中秒杀接口并发量剧增与负载优化策略探讨

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; 目录 引言 1. 实施限流措施 1.1 令牌桶算法&#xff1a; 1.2 漏…

Qt Creator仿Visual Studio黑色主题

转自本人博客&#xff1a;Qt Creator仿Visual Studio黑色主题 1.演示 配置文件和步骤在后面&#xff0c;先看成品&#xff0c;分别是QWidget和QML的代码编写界面&#xff1a; 2. 主题配置文件 下载链接&#xff1a;QtCreator _theme_VS_dark.xml 也可以自己新建一个xml文件&…

指定地区|教培老师自费赴美国首都华盛顿特区乔治敦大学访问交流

W老师将出国访学目标定在美国东西部城市&#xff0c;但其学术背景薄弱&#xff0c;没有论文等科研成果&#xff0c;只有教研培训经历。我们在申请时扬长避短&#xff0c;突出优势&#xff0c;效果显著。最终获得了首都华盛顿特区的乔治敦大学访问学者offer。该市拥有多所优质公…

LLM - 词向量 Word2vec

1. 词向量是一个词的低维表示&#xff0c;词向量可以反应语言的一些规律&#xff0c;词意相近的词向量之间近乎于平行。 2. 词向量的实现&#xff1a; &#xff08;1&#xff09;首先使用滑动窗口来构造数据&#xff0c;一个滑动窗口是指在一段文本中连续出现的几个单词&#x…

零信任网络安全

随着数字化转型的发生&#xff0c;网络边界也在不断被重新定义&#xff0c;因此&#xff0c;组织必须使用新的安全方法重新定义其防御策略。 零信任是一种基于“永不信任&#xff0c;永远验证”原则的安全方法&#xff0c;它强调无论在公司内部或外部&#xff0c;任何用户、设…

ST Smart Things Sentinel:一款针对复杂IoT协议的威胁检测工具

关于ST Smart Things Sentinel ST Smart Things Sentinel&#xff0c;简称ST&#xff0c;是一款功能强大的安全工具&#xff0c;广大研究人员可以使用该工具检测物联网 (IoT) 设备使用的复杂协议中的安全威胁。 在不断发展的联网设备领域&#xff0c;ST Smart Things Sentinel…

【人工智能】-- 搜索技术(状态空间法)

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a; 专业知识 文章目录 &#x1f349;引言 &#x1f348;介绍 &#x1f349;状态空间法 &#x1f348;状态空间的构成 &#x1f34d;状态 &#x1f34d;算符…

opencv读取视频文件夹内视频的名字_时长_帧率_分辨率写入excel-cnblog

看视频的时候有的视频文件名贼长。想要翻看&#xff0c;在文件夹里根本显示不出来&#xff0c;缩短又会丢失一些信息&#xff0c;所以我写了一份Python代码&#xff0c;直接获取视频的名字&#xff0c;时长&#xff0c;帧率&#xff0c;还有分辨率写到excel里。 实际效果如下图…

一、openGauss详细安装教程

一、openGauss详细安装教程 一、安装环境二、下载三、安装1.创建omm用户2.授权omm安装目录3.安装4.验证是否安装成功5.配置gc_ctl命令 四、配置远程访问1.配置pg_hba.conf2.配置postgresql.conf3.重启 五、创建用户及数据库 一、安装环境 Centos7.9 x86openGauss 5.0.1 企业版…

【论文速读】| 用于安全漏洞防范的人工智能技术

本次分享论文&#xff1a;Artificial Intelligence Techniques for Security Vulnerability Prevention 基本信息 原文作者&#xff1a;Steve Kommrusch 作者单位&#xff1a;Colorado State University, Department of Computer Science, Fort Collins, CO, 80525 USA 关键…

4:表单和通用视图

表单和通用视图 1、编写一个简单的表单&#xff08;1&#xff09;更新polls/detail.html文件 使其包含一个html < form > 元素&#xff08;2&#xff09;创建一个Django视图来处理提交的数据&#xff08;3&#xff09;当有人对 Question 进行投票后&#xff0c;vote()视图…

初识STM32:寄存器编程 × 库函数编程 × 开发环境

STM32的编程模型 假如使用C语言的方式写了一段程序&#xff0c;这段程序首先会被烧录到芯片当中&#xff08;Flash存储器中&#xff09;&#xff0c;Flash存储器中的程序会逐条的进入CPU里面去执行。 CPU相当于人的一个大脑&#xff0c;虽然能执行运算和执行指令&#xff0c;…

ASP.NET Core----基础学习01----HelloWorld---创建Blank空项目

文章目录 1. 创建新项目--方式一&#xff1a; blank2. 程序各文件介绍&#xff08;Project name &#xff1a;ASP.Net_Blank&#xff09;&#xff08;1&#xff09;launchSettings.json 启动方式的配置文件&#xff08;2&#xff09;appsettings.json 基础配置file参数的读取&a…

分布式锁(仅供自己参考)

分布式锁&#xff1a;满足分布式系统或集群式下多进程可见并且互斥的锁&#xff08;使用外部的锁&#xff0c;因为如果是集群部署&#xff0c;每台服务器都有一个对应的tomcat&#xff0c;则每个tomcat的jvm就不同&#xff0c;锁对象就不同&#xff08;加锁的机制&#xff0c;每…

【UML用户指南】-32-对体系结构建模-部署图

目录 1、对嵌入式系统建模 2、对客户/服务器系统建模 3、对全分布式系统建模 部署图展示运行时进行处理的结点和在结点上生存的制品的配置。 部署图用来对系统的静态部署视图建模。 在UML中&#xff0c;可以 1&#xff09;利用类图和制品图来思考软件的结构&#xff0c; …

【初阶数据结构】1.算法复杂度

文章目录 1.数据结构前言1.1 数据结构1.2 算法1.3 如何学好数据结构和算法 2.算法效率2.1 复杂度的概念2.2 复杂度的重要性 3.时间复杂度3.1 大O的渐进表示法3.2 时间复杂度计算示例3.2.1 示例13.2.2 示例23.2.3 示例33.2.4 示例43.2.5 示例53.2.6 示例63.2.7 示例7 4.空间复杂…

音视频开发—FFmpeg处理流数据的基本概念详解

文章目录 多媒体文件的基本概念相关重要的结构体操作数据流的基本步骤1.解复用&#xff08;Demuxing&#xff09;2.获取流&#xff08;Stream&#xff09;3. 读取数据包&#xff08;Packet&#xff09;4. 释放资源&#xff08;Free Resources&#xff09;完整示例 多媒体文件的…