Linux之多线程互斥

server/2024/12/23 18:14:20/

目录

线程互斥的概念

原子性 

线程互斥的引入

互斥锁

互斥锁的创建 

互斥锁的静态初始化

互斥锁的动态初始化

互斥锁的销毁

互斥锁加锁

互斥锁解锁 

互斥锁加锁和解锁的原理


上一期我们学习了线程控制,线程控制就是根据pthread线程库提供的线程接口对线程进行一系列的操作,本期我们学习的内容就是,当多个线程运行时,怎么保证线程合规,合理的运行。

线程互斥的概念

我们通过一个情景为大家引入线程互斥等相关概念。

在学生时代,中午放学铃声一响,同学们一窝蜂的跑向了学校的食堂去吃饭,食堂的窗口是有限的但是干饭的学生的数量却是很多的,所以势必会排成多列很长的队伍。队伍排头的人可以率先吃到饭,队伍末尾的人最后吃到饭,这也是同学们下课后快马加鞭的原因。按照计算机中的专业术语来说,队头和队尾决定吃饭的优先级。但是大家仔细想想,其实在排队打饭的过程中可能有这样几个不成文的规定。

1.在每个打饭的队伍中,只有队头的人可以打饭,且队头的人打饭的时候,其他人只能等着,不能去打饭。这保证了打饭的合规性

2.即使一个同学的饭量再大,打完饭之后就立即吃完,也不能接着打,打完饭之后,必须排在队列的末尾。这保证打饭的合理性

通过上述两个场景,引入了互斥和同步的概念。

互斥保证了多线程在访问临界资源时的合规性。即一个线程在访问临界资源的时候,其他线程不能不能访问。

同步保证了多线程在访问临界资源时的合理性。即一个线程在访问完临界资源之后,不能立刻去再次访问临界资源,必须被加入等待队列,等待其它线程访问完临界资源后再次访问

我们上文刚提到了临界资源,那么到底什么是临界资源呢?

我们称能被多个线程访问的资源为临界资源,我们称访问临界资源的代码为临界区。 最简单的理解就是我们创建的一个全局变量可以被多个线程访问。生活中的例子就是电影院的座位可以被多个顾客使用。  

原子性 

原子性:一件事,要做就做完,要么就不做。一般我们通过原子性来保证互斥。比如++和--操作都是原子性的。 

本期我们主要来研究线程互斥的相关内容。

线程互斥的引入

先看下述代码。

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>int ticket=100;void* route(void* args)
{char* val =(char*)args;while(1){if(ticket > 0){sleep(1);printf("%s sells ticket: %d\n",val ,ticket);ticket--;}else{break;}}}int main()
{pthread_t t1,t2,t3,t4;pthread_create(&t1,NULL,route,"thead 1");pthread_create(&t2,NULL,route,"thead 2");pthread_create(&t3,NULL,route,"thead 3");pthread_create(&t4,NULL,route,"thead 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);}

我们创建了4个线程,让这四个线程去抢我们定义的ticket票。注意,因为使用的是原生线程库的接口,所以在进行代码的编译时,必须以-pthread进行库的声明,不然会编译报错。运行结果如下。

通过运行结果截图我们惊奇的发现,票数竟然被减为了负数,这是为什么?到底处出了什么问题呢?

这是因为,ticket是一个临界资源,上述四个线程在访问临界资源的时候,因为没有互斥条件的约束。还剩最后一张票时,4,3,2,1号线程来了,发现票数都大于0,所以都进入了if语句,但是最终ticket的--操作,肯定是有先后顺序的,所以就会导致,4号线程一直到1号线程都会对票数进行--操作,最终将票数减为负数。

上述的代码,就是多线程在访问临界资源ticket的场景,不难发现,这种场景会出先很严重的问题,就是将票数减为了负数,那么该如何解决这种问题呢?这就需要用到我们本节课的知识,采用互斥的思想去解决。上文我们也提到了,互斥我们一般用原子性来实现,那么原子性我们一般采用什么实现呢?这就要引入我们本期的重点------互斥锁,我们通过互斥锁来实现线程访问临界资源的原子性,进一步实现多线程的互斥。

互斥锁

互斥锁是一种通过原子性操作保证多个线程访问临界资源时,达到互斥访问,保证线程安全的机制。

互斥锁的创建 

pthread_mutex_t mutex; 

互斥锁的静态初始化

pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;

采用宏进行初始化。

互斥锁的动态初始化

pthred_mutex_init((pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr) ;

第一个参数mutex为要初始化的互斥锁量,第二个参数我们一般设置为NULL。

互斥锁的销毁

 pthread_mutex_destroy(&mutex);

注意:静态方法初始化的互斥锁不需要销毁

互斥锁加锁

pthread_mutex_lock(&mutex);

互斥锁解锁 

pthread_mutex_unlock(&mutex); 

代码如下。

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>int ticket=100;
pthread_mutex_t mutex;void* route(void* args)
{char* val =(char*)args;while(1){pthread_mutex_lock(&mutex);if(ticket > 0){sleep(1);printf("%s sells ticket: %d\n",val ,ticket);ticket--;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}}int main()
{pthread_t t1,t2,t3,t4;pthread_mutex_init(&mutex,NULL);pthread_create(&t1,NULL,route,"thead 1");pthread_create(&t2,NULL,route,"thead 2");pthread_create(&t3,NULL,route,"thead 3");pthread_create(&t4,NULL,route,"thead 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);pthread_mutex_destroy(&mutex);
}

运行结果如下。 

不难发现,此时ticket的数量已经恢复了正常,没有再出现负数。 那么整个互斥锁的原理是什么呢?接下来我们一起学习。

互斥锁加锁和解锁的原理

加锁和解锁的原理都是通过如下汇编代码来实现的。

 CPU中有很多的寄存器,多个线程在进行临界资源的访问时,会将自己的上下文数据保存在CPU特定的寄存器中,这些寄存器是多个线程共享的,但是寄存器中的数据,是每个线程独有的,互斥锁一般只有一把,当一个线程进行了加锁时,其它线程肯定是不能再次进行加锁的,这我们很容易想出来,但是计算机不知道,计算机时如何根据只有一把互斥锁的前提,从而使得一把锁一次只允许一个线程进行加锁呢?

互斥锁在多线程的情境下,只有一把互斥锁,所以,一般情况下,互斥锁的值我们初始化为1。在进行加锁时,先将线程cpu内部寄存器中的值设为0,然后与内存中mutex的值进行交换,然后进去if语句,如果寄存器中的值大于0,那么就证明申请锁成功并加锁,否则就挂起等待。如果此时第一个线程已经完成了加锁,那么此时寄存器中的值就为1,而内存中的mutex的值就为0,所以当下一个线程来再次申请锁时,即使当前线程的寄存器中的值与内存中的mutex的值发生了交换,因为之前第一个线程已经进行了加锁,并没有解锁,所以mutex的值还为0,所以当前线程的寄存器的值在交换前后并没有发生变化,所以只能挂起等待。在进行解锁的时候,会将mutex的值重新设置为1,然后后续线程再次进行lock中对应的汇编操作。

因为lock(加锁)和unlock(解锁)之间是访问临界资源的代码,所以加锁之后的线程很有可能在执行临界资源的代码过程中, 因为时间片到了被切换走,但是即使被切换走了,也是抱着锁被切走挂起,所以一直没有解锁,只有等到这个线程被再次唤醒,执行完临界区的代码,并解锁,其它线程才有加锁的机会,才有访问临界资源的机会。所以即要么一个线程没有加锁,要么加了锁之后执行完了临界区的代码并解锁,这样对其它的线程才是具有意义的。也就是通过互斥锁的这种加锁和解锁方式,实现了多个线程访问临界资源的互斥。

以上便是本期线程互斥的所有相关内容。

本期内容到此结束^_^


http://www.ppmy.cn/server/152549.html

相关文章

mac iterm2 使用 lrzsz

前言 mac os 终端不支持使用 rz sz 上传下载文件&#xff0c;本文提供解决方法。 mac 上安装 brew install lrzsz两个脚本 注意&#xff1a;/usr/local/bin/iterm2-send-zmodem.sh 中的 sz命令路径要和你mac 上 sz 命令路径一致。 /usr/local/bin/iterm2-recv-zmodem.sh 中…

Linux 端口操作

安装netstat yum -y install net-tools 检测端口占用 netstat -npl | grep "端口" 安装lsof lsof yum -y install lsof 检测端口占用 lsof -i :端口号 安装nc yum -y install nc 查看对方端口是否开放 nc -vz 对方ip 对方端口 安装telnet telnet yum -y in…

信奥赛四种算法描述

#include <iostream> #include <iomanip> using namespace std;// 使用unsigned long long类型来尽量容纳较大的结果&#xff0c;不过实际上这个数值极其巨大&#xff0c;可能最终仍会溢出 // 更好的方式可以考虑使用高精度计算库&#xff08;如GMP等&#xff09;来…

适用于.net的操作excel的库

目录 目录 知识点概要 知识点具体说明(使用步骤、解释) 使用场景及示例 1. EPPlus 2. NPOI 3. ClosedXml 4. SpreadsheetLight 5.ExcelDataReader 开源库 6. ExcelDataWriter 总结 知识点概要 使用Excel来导入和导出数据&#xff0c;在日常工作中使用频率是相当高的&…

情报信息收集能力

红队专题-Web渗透之资产思路框架知识整理 钓鱼社工 钓鱼自动化zip域名ARP欺骗快捷方式ToolsburpsuiteApp 抓包ffuf模糊测试QingScanWiresharkCloudCFEn-Decodeffffffff0xInfodirbdirmapdirsearchdnsenum使用测试常规使用使用字典文件进行dns查询子域名暴力查询部分C类IP地址IP块…

鸿鹄电子招投标系统源码实现与立项流程:基于Spring Boot、Mybatis、Redis和Layui的企业电子招采平台

随着企业的快速发展&#xff0c;招采管理逐渐成为企业运营中的重要环节。为了满足公司对内部招采管理提升的要求&#xff0c;建立一个公平、公开、公正的采购环境至关重要。在这个背景下&#xff0c;我们开发了一款电子招标采购软件&#xff0c;以最大限度地控制采购成本&#…

html(超文本标记语言)

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&…

陪诊小程序搭建,打造一站式陪诊服务

当下&#xff0c;陪诊市场正在持续火热发展&#xff0c;在全国医疗行业中&#xff0c;陪诊师成为了一个重要的就医方式。陪诊师的出现在快节奏生活下显得尤为重要&#xff0c;为不少没有时间陪老人去医院的家庭以及对医院不熟悉的提供了便利&#xff0c;满足了众多患者及其家属…