【1++的Linux】之线程(二)

👍作者主页:进击的1++
🤩 专栏链接:【1++的Linux】

文章目录

  • 一,对上一篇内容的补充
  • 二,Linux线程互斥
    • 1. 互斥的引出
    • 2. 互斥量
    • 3. 剖析锁的原理

一,对上一篇内容的补充

线程创建: pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID

**线程终止:**需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

线程等待: 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。(线程等待的原因)

int pthread_join(pthread_t thread, void **value_ptr);

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数
    PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参
    数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

线程分离: 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

二,Linux线程互斥

1. 互斥的引出

我们再来回顾一下我们以前曾提到过的临界资源,临界区和原子性。

临界资源:被多个执行流所共享的资源叫做临界资源。
临界区:每个线程内部执行访问临界资源的代码叫做临界区。
原子性:在别人看来只有两种状态,做一件事情,要么没做,要么做完。

首先我们来回答为什么要有线程互斥。

我们来看一段代码:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
using namespace std;int tickets=1000;
void* get_tickets(void* argv)
{while(true){if(tickets>0){cout<<"我是:"<<pthread_self()<<" "<<"我抢的第"<<tickets<<"票"<<endl;tickets--;}else{break;}}cout<<"票完了"<<endl;return nullptr;
}int main()
{pthread_t tid[3];for(int i=0;i<3;i++){pthread_create(tid+i,nullptr,get_tickets,(void*)"thread");//创建多个线程}//等待线程for(int i=0;i<3;i++){pthread_join(tid[i],nullptr);}return 0;
}

在这里插入图片描述
我们看到,最终的结果竟然有两个线程抢到了同一个编号的票,这样岂不是一个作为我们卖出去了两张甚至更多的票。

这是为什么呢?
我们用下面这张剖析图来理解:
在这里插入图片描述

有如下场景:线程A先执行,票数减减的步骤在我们看到就只有一行,实际转换为汇编代码后是有三条语句,图中我已经写出,当A执行完前两步,要将数据写入内存中去时,因为时间片等某种原因,其被切换了下来,换进程B去执行,此时在内存中票的数仍然为1000,所以B拿到的仍然是编号为1000的票,因此就发生了上述结果。
因此就要有互斥的存在了!!!

多线程是共享地址空间的,所以有很多资源都是共享的。
这种方式带来的优势:方便了线程间的通信
缺点:并发访问一些共享的数据时,回由于时序问题而导致数据不一致的问题。

那么什么是互斥呢?
我们先来看其概念:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
以上述代码为例,那么我们的临界资源就是票的数量,临界区就为抢票过程的一段代码

在这里插入图片描述
互斥就是对临界区的保护的一种方式,其本质就是保护临界资源

2. 互斥量

要解决上述数据不一致的问题,需要做到三点:

  1. 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  2. 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  3. 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
    要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量

锁的初始化:
在这里插入图片描述

锁的初始化有两种,一种是用函数做初始化,将你的锁的地址传入进去,属性可以设置为nullptr,另一种是对于全局的锁或者是static修饰的锁,可以直接用宏PTHREAD_MUTEX_INITIALIZER 进行初始化。

锁的销毁:

pthread_mutex_destroy是销毁锁。
销毁互斥量需要注意:
使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁

加锁和解锁:
在这里插入图片描述

int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);//加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
返回值:成功返回0,失败返回错误号。
调用 pthread_ lock 时:

  1. 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。
  2. 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。
    trylock函数,:
    这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。

有了锁之后我们对抢票系统做出改进:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
using namespace std;pthread_mutex_t mt=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;//创建锁并初始化
int tickets=1000;
void* get_tickets(void* argv)
{(void*)argv;while(true){pthread_mutex_lock(&mt);//加锁if(tickets>0){cout<<"我是:"<<pthread_self()<<" "<<"我抢的第"<<tickets<<"票"<<endl;tickets--;pthread_mutex_unlock(&mt);}else{pthread_mutex_unlock(&mt);break;}}cout<<"票完了"<<endl;return nullptr;
}int main()
{pthread_t tid[3];for(int i=0;i<3;i++){pthread_create(tid+i,nullptr,get_tickets,(void*)"thread");//创建多个线程}//等待线程for(int i=0;i<3;i++){pthread_join(tid[i],nullptr);}return 0;
}

在这里插入图片描述
此时我们可以看到运行结果就达到了我们所期望的。

下面我们换一种锁的初始化方式来进行验证:

#define TH_NUM 5class Get_tickets
{public:Get_tickets(string& name,pthread_mutex_t* mut):_name(name),_mut(mut){}public:string _name;pthread_mutex_t* _mut;};void* get_tickets(void* argv)
{Get_tickets* Lock=(Get_tickets*)argv;while(true){pthread_mutex_lock(Lock->_mut);if(tickets>0){cout<<"I am "<<Lock->_name<<": "<<tickets<<endl;tickets--;pthread_mutex_unlock(Lock->_mut);usleep(100000);}else{pthread_mutex_unlock(Lock->_mut);break;}}cout<<"票完了"<<endl;
}
int main()
{pthread_t tid[TH_NUM];pthread_mutex_t mut;pthread_mutex_init(&mut,nullptr);string name="thread";for(int i=0;i<TH_NUM;i++){ Get_tickets* pLock=new Get_tickets(name,&mut);pLock->_name+=to_string(i+1);pthread_create(tid+i,nullptr,get_tickets,(void*)pLock);}for(int i=0;i<TH_NUM;i++){pthread_join(tid[i],nullptr);}return 0;
}

在这里插入图片描述
我们发现结果符合我们的预期。

3. 剖析锁的原理

由于我们对临界区加了锁,因此多个执行流在访问临界值的时候都是串行的,也就是说每次只让一个执行流区访问临界资源直到出了临界区,也就是解锁后才回再让这些执行流去竞争进入临界区。(对于临界区,我们的临界区要尽量短小精悍,因为锁是回影响执行效率的,这违背了我们创建线程的初衷,因此非必要不适用锁) 我们的临界资源加了锁后我们就可以说它是原子的。我们在访问临界资源时,先访问的是锁,先会去判断是否已经加锁了,并且会有多个执行流看到它,那么锁是不是临界资源呢?或者说是互斥锁是不是原子的呢?锁自己都保护不好自己怎么去保护别人,那么该如何去保证锁的安全呢?

我们先抛出答案,锁是具有原子性的。

我们来看看关于加锁和解锁的两段伪代码:
在这里插入图片描述
我们再次以一张图来理解这段伪代码:
在这里插入图片描述
我们有如下场景:
A线程正在进行加锁的过程,我们可以把申请的这把锁中的内容 “1” 看作一个令牌(一个锁只有一个)先是将0写入特定的寄存器当中,接着将锁的内容和寄存器中的0进行交换(这一步在汇编中只有一行代码,因此这一操作也是原子的) 若,这是,A线程被切换掉,B线程执行,此时会在从第一步开始执行,将0写入,然后交换,但这是交换到寄存器中的值是A进行交换时交换过去的0,因此在判断是,其会被挂起等待,此时A线程被换上去继续执行,恢复其上下文数据后,(这段数据中,也会记录A上次执行到了那一步),此时寄存器中的值就为恢复上来的1,进行判断后,加锁成功。
这就好比上面所提到的只有一个令牌,只要执行完交换语句后,A就拿到了这个令牌,成为它上下文中的一部分,哪怕被切下去,也没有关系,因为,寄存器只有一份,但寄存器中的数据可以有很多分。寄存器中的内容,是每一个执行流私有的。
此时B虽然被调度执行,但令牌已经没了,所以B只能等待。

对于解锁,就是将令牌归还与锁,这一动作也是有原子的。归还后,等待的线程会再次重复上述争令牌的过程。

交换的现象:内存<---->寄存器
交换的本质:原本锁中的数据:共享---->私有

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

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

相关文章

nginx--install

1. ubuntu 1.1 下载并解压依赖 每个包去各自官网下载 stable 版就行。 tar xzvf nginx-1.24.0.tar.gz tar xzvf openssl-3.1.4.tar.gz tar xzvf pcre2-10.42.tar.gz tar xzvf zlib-1.3.tar.gz1.2 配置及安装 参数含义详见 nginx 官网 cd nginx-1.24.0./configure --pre…

随时随地时时刻刻使用GPT类应用

疑问 很多人说GPT的广泛使用可能会使人们失业&#xff0c;会对一些互联网公司的存活造成挑战&#xff0c;那么这个说法是真的吗&#xff1f; 这个说法并不完全准确。虽然GPT等AI技术的广泛应用可能会对某些行业和职业产生影响&#xff0c;但并不意味着它会导致人们失业或互联网…

Zookeeper3.7.1分布式安装部署

上传安装文件到linux系统上面 解压安装文件到安装目录 [zhangflink9wmwtivvjuibcd2e package]$ tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz -C /opt/software/3. 修改解压文件名 [zhangflink9wmwtivvjuibcd2e software]$ mv apache-zookeeper-3.7.1-bin/ zookeeper-3.7…

2021年电工杯数学建模B题光伏建筑一体化板块指数发展趋势分析及预测求解全过程论文及程序

2021年电工杯数学建模 B题 光伏建筑一体化板块指数发展趋势分析及预测 原题再现&#xff1a; 国家《第十四个五年规划和 2035 年远景目标纲要》中提出&#xff0c;将 2030 年实现“碳达峰”与 2060 年实现“碳中和”作为我国应对全球气候变暖的一个重要远景目标。光伏建筑一体…

力扣最热一百题——盛水最多的容器

终于又来了。我的算法记录的文章已经很久没有更新了。为什么呢&#xff1f; 这段时间都在更新有关python的文章&#xff0c;有对python感兴趣的朋友可以在主页找到。 但是这也并不是主要的原因 在10月5号我发布了我的第一篇博客&#xff0c;大家也可以看见我的每一篇算法博客…

Canal同步Mysql数据到ES以及Springboot项目查询ES数据

1、环境准备 服务器&#xff1a;Centos7 Jdk版本&#xff1a;1.8 Mysql版本&#xff1a;5.7.44 Canal版本&#xff1a;1.17 Es版本&#xff1a;7.12.1 kibana版本&#xff1a;7.12.1 软件包下载地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1jRpCJP0-hr9aI…

Elasticsearch:在 ES|QL 中使用 DISSECT 和 GROK 进行数据处理

目录 DISSECT 还是 GROK&#xff1f; 或者两者兼而有之&#xff1f; 使用 DISSECT 处理数据 Dissect pattern 术语 例子 DISSECT 关键修饰符 右填充修饰符 (->) 附加修饰符 () 添加顺序修饰符&#xff08; 和 /n&#xff09; 命名的跳过键&#xff08;&#xff1f…

LLM之Prompt(一):5个Prompt高效方法在文心一言3.5的测试对比

在Effective Prompt: 编写高质量Prompt的14个有效方法文中我们了解了14个编写Prompt的方法&#xff08;非常感谢原作者&#xff09;&#xff0c;那么这些Prompt在具体大模型中的效果如何呢&#xff1f;本文以百度文心一言3.5版本大模型在其中5个方法上做个测试对比。 第1条&am…

MySQL复习总结(二):进阶篇(索引)

文章目录 一、存储引擎1.1 MySQL体系结构1.2 存储引擎介绍1.3 存储引擎特点1.4 存储引擎选择 二、索引2.1 基本介绍2.2 索引结构2.3 索引分类2.4 索引语法2.5 SQL性能分析2.6 索引使用2.6.1 最左前缀法则2.6.2 范围查询2.6.3 索引失效情况2.6.4 SQL提示2.6.5 覆盖索引2.6.6 前缀…

linux中if条件判断,case...esac,function学习

第一、 if [ 判断式 ] ; then fi 注意&#xff1a;中括号和判断式之间的空格&#xff0c;否则会报错&#xff0c;上案例 第二个图的12行&#xff0c;中括号和条件判断如果没有空格&#xff0c;则会提示缺号‘】’&#xff0c;如第二个图最上面的提示。所以使用中括号的格式…

ts学习01-开发环境搭建

环境 nodejs 18 npm 安装typescript npm install typescript # 如果上面太慢&#xff0c;可以执行下面的方法 npm install typescript --registryhttps://registry.npm.taobao.orgHelloWorld 新建index.ts console.log("hello ts");执行下面命令进行编译 npx t…

GPT-4V:AI在教育领域的应用

OpenAI于9月25日发布了最新的GPT-4V模型&#xff0c;为ChatGPT引入了语音和图像功能&#xff0c;为用户提供更多元化的使用方式。这次更新将为用户带来更便捷、直观的交互体验&#xff0c;用户可以直接拍照上传并针对照片内容提出问题。OpenAI的最终目标是构建安全、有益的人工…

2.3 - 网络协议 - ICMP协议工作原理,报文格式,抓包实战

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 ICMP协议 1、ICMP协议工作原理2、ICMP协议报文格式…

Linux网络编程04

更高效的零拷贝 发送方过程零拷贝 sendfile 发送文件方的零拷贝&#xff0c;虽然之前我们就可以使用mmap来实现零拷贝但是存在一个方法sendfile也可以直接实现数据从内核区发送到网络发送区socket 直接把内核里面你的文件数据不经过用户态&#xff0c;直接发送给另外一个文件…

如何对ppt文件设置修改权限?

PPT文件会应用在会议、演讲、课件等工作生活中&#xff0c;当我们制作好了PPT之后&#xff0c;保护内容防止在演示时出错是很重要的&#xff0c;那么如何将PPT文件设置成禁止修改模式呢&#xff1f;今天分享几个方法给大家。 方法一 将PPT文件直接保存或者另存为一份文件&…

C++入门学习(1)命名空间和输入输出

前言 在C语言和基本的数据结构学习之后&#xff0c;我们终于迎来了期待已久的C啦&#xff01;C发明出来的意义就是填补一些C语言的不足&#xff0c;让我们更加方便的写代码&#xff0c;所以今天我们就来讲一下C语言不足的地方和在C中的解决办法&#xff01; 一、命名空间 在学习…

前端框架Vue学习 ——(五)前端工程化Vue-cli脚手架

文章目录 Vue-cliVue项目-创建Vue项目-目录结构Vue项目-启动Vue项目-配置端口Vue项目开发流程 Vue-cli 介绍&#xff1a;Vue-cli 是 Vue 官方提供的一个脚手架&#xff0c;用于快速生成一个 Vue 的项目模版 安装 NodeJS安装 Vue-cli npm install -g vue/cliVue项目-创建 图…

嬴图 | LLM+Graph:大语言模型与图数据库技术的协同

前言 2022年11月以来&#xff0c;大语言模型席卷全球&#xff0c;在自然语言任务中表现卓越。尽管存在一系列伦理、安全等方面的担心&#xff0c;但各界对该技术的热情和关注并未减弱。 本文不谈智能伦理方面的问题&#xff0c;仅集中于Ulitpa嬴图在应用中的一些探索与实践&a…

01-单节点部署clickhouse及简单使用

1、下载rpm安装包&#xff1a; 官网&#xff1a;https://packages.clickhouse.com/rpm/stable/ clickhouse19.4版本之后只需下载3个rpm安装包&#xff0c;上传到节点目录即可 2、rpm包安装&#xff1a; 安装顺序为conmon->server->client 执行 rpm -ivh ./clickhouse-…

【深度学习 AIGC】stable diffusion webUI 使用过程,参数设置,教程,使用方法

文章目录 docker快速启动vae.ckpt或者.safetensorsCFG指数/CFG Scale面部修复/Restore facesRefinerTiled VAEClip Skipprompt提示词怎么写roop Upscaler visibility (if scale 1) docker快速启动 如果你想使用docker快速启动这个项目&#xff0c;你可以按下面这么操作&#…