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

文章目录

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

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

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

相关文章

Vue使用Echarts(入门级)

最终效果&#xff1a; npm install echarts --save // 先安装echarts<template><!-- 创建一个dom区域用于挂载echarts图表 --><div id"chart" style"width: 600px;height:500px;"/> </template> <script> import * as ech…

WPF依赖附加属性

依赖附加属性的定义 基本过程&#xff1a;声明、注册、包装 依赖附加属性必须在依赖对象&#xff0c;附加属性不一定&#xff0c;关注的是被附加的对象是否是依赖对象 快捷方式&#xff1a;propa tab 关键字&#xff1a;RegisterAttached // 方法封装 public static int …

4.MkDocs样式

学习 Admonitions(警告) - Material for MkDocs (wdk-docs.github.io) 提示 - Material for MkDocs 中文文档 (llango.com) Buttons(按钮) - Material for MkDocs (wdk-docs.github.io) 建议去看这些网站&#xff0c;更为详细。 常用功能 便利贴 ​​ 开启 markdown_ex…

FL Studio 24.1.1.4234 (Windows) / 24.1.1.3884 (Mac OS X)

FL Studio 24.1.1.4234 (Windows) / 24.1.1.3884 (Mac OS X) 主页多媒体音频编辑FL Studio 24.1.1.4234 (Windows) / 24.1.1.3884... FL Studio 图标 FL Studio&#xff08;前身为 FruityLoops&#xff09;是一款功能强大的音乐制作环境或数字音频工作站&#xff08;DAW&#x…

基于Java的科大讯飞大模型API调用实现

写在前面&#xff1a;因为现在自己实习的公司新拓展的一个业务是结合AI的低代码平台&#xff0c;我负责后端的开发&#xff0c;之前一直都是直接使用gpt或者文心一言等ui界面来直接使用大模型&#xff0c;从来没有自己调接口过&#xff0c;所以本文记录一下自己第一次使用大模型…

【密码学】分组密码概述

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

【无聊找点事干】局域网内把window 搭建为代理服务器上网

场景描述 同一局域网内&#xff0c;server 2012可以上网&#xff0c;window 10无法上网。现在将电脑server 2012设置为代理服务器&#xff0c;使得window 10可以通过server 2012访问公网。 server 2012&#xff1a;服务端 安装代理服务器软件&#xff1a;CCProxy点击‘设置’…

『大模型笔记』GraphRAG:利用复杂信息进行发现的新方法!

GraphRAG:利用复杂信息进行发现的新方法! 文章目录 一. GraphRAG:利用复杂信息进行发现的新方法!1. 将RAG应用于私人数据集2. 整个数据集的推理3. 创建LLM生成的知识图谱4. 结果指标5. 下一步二. 参考文献微软官方推文:https://www.microsoft.com/en-us/research/blog/gra…

综合安全防护

题目 1,DMZ区内的服务器,办公区仅能在办公时间内(9:00-18:00)可以访问,生产区的设备全天可以访问. 2,生产区不允许访问互联网,办公区和游客区允许访问互联网 3,办公区设备10.0.2.10不允许访问DMz区的FTP服务器和HTTP服务器,仅能ping通10.0.3.10 4,办公区分为市场部和研发部,研…

activemq-CVE-2022-41678

Apache ActiveMQ Jolokia 后台远程代码执行漏洞 Apache ActiveMQ在5.16.5&#xff0c;5.17.3版本及以前&#xff0c;后台Jolokia存在一处任意文件写入导致的远程代码执行漏洞。 启动环境 admin/admin 方法一&#xff1a;利用poc 这个方法受到ActiveMQ版本的限制&#xff0c;因…

化妆品3D虚拟三维数字化营销展示更加生动、真实、高效!

随着人们越来越追求高速便捷的生活工作方式&#xff0c;企业在营销市场也偏国际化&#xff0c;借助VR全景制作技术&#xff0c;将企业1:1复刻到云端数字化世界&#xff0c;能带来高沉浸式的逼真、震撼效果。 通过我们独特的漫游点自然场景过渡技术&#xff0c;您将置身于一个真…

Java | Leetcode Java题解之第225题用队列实现栈

题目&#xff1a; 题解&#xff1a; class MyStack {Queue<Integer> queue;/** Initialize your data structure here. */public MyStack() {queue new LinkedList<Integer>();}/** Push element x onto stack. */public void push(int x) {int n queue.size();…

Qt Creator仿Visual Studio黑色主题

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

理解局域网技术:从基础到进阶

局域网&#xff08;LAN&#xff09;是在20世纪70年代末发展起来的&#xff0c;起初主要用于连接单位内部的计算机&#xff0c;使它们能够方便地共享各种硬件、软件和数据资源。局域网的主要特点是网络为一个单位所拥有&#xff0c;地理范围和站点数目均有限。 局域网技术在计算…

后端——全局异常处理

一、老办法try-catch 当我们执行一些错误操作导致程序报错时&#xff0c;程序会捕捉到异常报错&#xff0c;这个异常会存在一个Exception对象里 那我们在spring boot工程开发时&#xff0c;当我们执行一个sql查询时报错了&#xff0c;那就会从最底层的Mapper层捕捉到Exceptio…

《RWKV》论文笔记

原文出处 [2305.13048] RWKV: Reinventing RNNs for the Transformer Era (arxiv.org) 原文笔记 What RWKV(RawKuv):Reinventing RNNs for the Transformer Era 本文贡献如下&#xff1a; 提出了 RWKV 网络架构&#xff0c;结合了RNNS 和Transformer 的优点&#xff0c;同…

Golang | Leetcode Golang题解之第225题用队列实现栈

题目&#xff1a; 题解&#xff1a; type MyStack struct {queue []int }/** Initialize your data structure here. */ func Constructor() (s MyStack) {return }/** Push element x onto stack. */ func (s *MyStack) Push(x int) {n : len(s.queue)s.queue append(s.queu…

基于信号量的生产者消费者模型

文章目录 信号量认识概念基于线程分析信号量信号量操作 循环队列下的生产者消费者模型理论认识代码部分 信号量 认识概念 信号量本质: 计数器 它也叫做公共资源 为了线程之间,进程间通信------>多个执行流看到的同一份资源---->多个资源都会并发访问这个资源(此时易出现…

边缘概率密度、条件概率密度、边缘分布函数、联合分布函数关系

目录 二维随机变量及其分布离散型随机变量连续型随机变量边缘分布边缘概率密度举例边缘概率密度 条件概率密度边缘概率密度与条件概率密度的区别边缘概率密度条件概率密度举个具体例子 参考资料 二维随机变量及其分布 离散型随机变量 把所有的概率&#xff0c;都理解成不同质量…