LINUX系统编程:多线程互斥

目录

1.铺垫

2.线程锁接口的认识

静态锁分配

动态锁的分配

互斥量的销毁

互斥量加锁和解锁

3.加锁版抢票

4.互斥的底层实现


1.铺垫

先提一个小场景,有1000张票,现在有4个进程,这四个进程疯狂的去抢这1000张票,看看会发生什么呢?

#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"class ticket
{
public:
static int tickets;//总共的票数
ticket(std::string &name)
:_name(name)
{}std::string &name()
{return _name;
}int _count = 0;//抢到多少票std::string _name;//线程的名字
};int ticket::tickets = 1000;void handler(ticket *t)
{while(true){if(t->tickets > 0){usleep(10000);std::cout<< t->name()<<"tickets-garbbing ticket:"<<t->tickets<<std::endl;t->tickets--;t->_count++;}else{break;}}
}using namespace mythread;//自己封装的线程库
int count = 4;
int main()
{std::vector<thread<ticket*>> threads;// 创建一批线程std::vector<ticket*> data;for (int i = 0; i < count; i++){std::string name = "thread" + std::to_string(i);ticket *t = new ticket(name);data.push_back(t);threads.emplace_back(handler, t, name);}//启动一批线程for(auto &t : threads){t.start();}//等待一批线程for(auto &t : threads){std::cout <<t.name() <<" wait sucess "<<std::endl;t.join();}//查看结果for(auto p : data){std::cout << p->_name<<" get tickets "<<p->_count<<std::endl;sleep(1);}return 0;
}


我们发现这四个线程竟然把票数抢到负数了,代码中已经判断if(t->tickets > 0)为什么票数还会减为0呢?

假设当前tickets只剩下1时。

thread0进行判断,thread0发现票数是大于0的,他就会进入循环,但是这个时候thread0的时间片到了,thread0进入等待队列。

thread1开始执行,thread1进行判断,thread1发现票数也是大于0的,进入循环,这个时候hread1的时间片到了,thread1进入等待队列。

thread2和thread3同样。

当cpu再次调度到thread0的时候,thread0对thickets--, thickets  = 0.

调度到thread1的时候,thread1对thickets--,tickets = -1.

thread2和thread3同样。

这也就解释了,为什票会抢到负数,究其原因就是我们抢票+判断的操作不是原子的,所以我们要通过互斥锁把这两个操作编程"原子"的,这个原子是在线程看来是原子的,不是真正意义上的原子。

也可以理解为把线程并行抢票,变成串行抢票,因为锁只有一把,一次只能有一个线程抢票

2.线程锁接口的认识

线程锁有两种分配方法,静态全局锁和局部锁

静态锁分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

动态锁的分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)

mutex:是要初始化的互斥量。

attr: nullptr。

互斥量的销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

注意:1.使用PTHREAT_MUTEX_INITIALIZER初始化的静态锁不用销毁。

           2.互斥量加锁了,就不要销毁了。

           3.销毁的互斥量,就不要在加锁了。

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值 : 成功返回 0, 失败返回错误码。

注意:lock的时候会有两种情况,一种是lock成功返回0。

          另一种是互斥量已经被lock,这时候该线程会阻塞等待。

3.加锁版抢票

静态锁板

#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定义一个全局锁class ticket
{
public:static int tickets; // 总共的票数ticket(std::string &name): _name(name){}std::string &name(){return _name;}int _count = 0;    // 抢到多少票std::string _name; // 线程的名字
};int ticket::tickets = 1000;void handler(ticket *t)
{// pthread_mutex_lock(&mutex);不能在这里上锁,在这里上锁,一个线程就把票抢完了while (true){pthread_mutex_lock(&mutex);if (t->tickets > 0){usleep(10000);std::cout << t->name() << "tickets-garbbing ticket:" << t->tickets << std::endl;t->tickets--;t->_count++;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}
}using namespace mythread; // 自己封装的线程库
int count = 4;
int main()
{std::vector<thread<ticket *>> threads;// 创建一批线程std::vector<ticket *> data;for (int i = 0; i < count; i++){std::string name = "thread" + std::to_string(i);ticket *t = new ticket(name);data.push_back(t);threads.emplace_back(handler, t, name);}// 启动一批线程for (auto &t : threads){t.start();}// 等待一批线程for (auto &t : threads){sleep(1);std::cout << t.name() << " wait sucess " << std::endl;t.join();}// 查看结果for (auto p : data){std::cout << p->_name << " get tickets " << p->_count << std::endl;sleep(1);}return 0;
}

动态锁板

在主函数定义一个局部锁

然后在ticket类中,增加一个互斥量,这个互斥量是要加引用的,为了所有的线程都能看见同一个锁。

#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"class ticket
{
public:static int tickets; // 总共的票数ticket(std::string &name, pthread_mutex_t &mutex): _name(name), _mutex(mutex){pthread_mutex_init(&mutex, nullptr);}std::string &name(){return _name;}int _count = 0;    // 抢到多少票std::string _name; // 线程的名字pthread_mutex_t &_mutex;//让所有的线程看到同一个锁
};int ticket::tickets = 1000;void handler(ticket *t)
{// pthread_mutex_lock(&mutex);不能在这里上锁,在这里上锁,一个线程就把票抢完了while (true){pthread_mutex_lock(&t->_mutex);if (t->tickets > 0){usleep(10000);std::cout << t->name() << "tickets-garbbing ticket:" << t->tickets << std::endl;t->tickets--;t->_count++;pthread_mutex_unlock(&t->_mutex);}else{pthread_mutex_unlock(&t->_mutex);break;}}
}using namespace mythread; // 自己封装的线程库
int count = 4;
int main()
{std::vector<thread<ticket *>> threads;// 创建一批线程pthread_mutex_t mutex;std::vector<ticket *> data;for (int i = 0; i < count; i++){std::string name = "thread" + std::to_string(i);ticket *t = new ticket(name, mutex);data.push_back(t);threads.emplace_back(handler, t, name);}// 启动一批线程for (auto &t : threads){t.start();}// 等待一批线程for (auto &t : threads){sleep(1);t.join();std::cout << t.name() << " wait sucess " << std::endl;}// 查看结果for (auto p : data){std::cout << p->_name << " get tickets " << p->_count << std::endl;sleep(1);}return 0;
}

运行结果

票是不会抢到负数了,但是出现了个问题。

为什么有的线程一个票也没抢到?

这个是因为不同的线程竞争能力不同,竞争能力强的就可以一直抢到锁,而竞争能力不强的就只能等待。

这个需要是用条件变量解决,下次介绍。

4.互斥的底层实现

互斥的底层是依赖swap 和exchange这两条指令的,这两条指令是原子的。

正常交换两个变量都需要,定义一个临时变量。

但是swap 和exchange这两条指令不用,可以直接交换,cpu寄存和内存的内容进行交换。

将lock和unlock的过程转化为伪代码(粗略只为了解原理)。

假设内存中存在一个mutex锁,mutex = 1时是解锁状态,mutex = 0是上锁状态 

我们发现1只有一个,哪个线程拿到1,哪个线程能继续执行代码,否则就要挂起等待。

重要的话

放在内存中的数据是所有线程共享的,但是一旦被加载到cpu中,就变成cpu的上下文数据,变成了线程私有的数据

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

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

相关文章

新书速览|Adobe Firefly:萤火虫:AI绘画快速创意设计

《Adobe Firefly&#xff1a;萤火虫&#xff1a;AI绘画快速创意设计》 本书内容 人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;浪潮的席卷已经变成不可阻挡的趋势&#xff0c;伴随着这种变化&#xff0c;在图形设计、图像制作、绘画领域也相应发生了…

什么是接口测试,我们如何实现接口测试?

1. 什么是接口测试 顾名思义&#xff0c;接口测试是对系统或组件之间的接口进行测试&#xff0c;主要是校验数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及相互逻辑依赖关系。其中接口协议分为HTTP,WebService,Dubbo,Thrift,Socket等类型&#xff0c;测试类型又主…

NewspaceGPT带你玩系列之SQL专家(强烈推荐)

目录 注册一个账号&#xff0c;用qq邮箱&#xff0c;然后登录选一个可用的Plus&#xff0c;不要选3.5探索GPT今天的主角是SQL Expert&#xff08;SQL 专家&#xff09;问题1&#xff1a;答1. 索引原因&#xff1a;优化措施&#xff1a;示例&#xff1a; 2. 查询设计原因&#x…

ros中teleop_twist_keyboard安装使用

目录 1.安装 2.使用 3.说明 1.安装 sudo apt-get install ros-noetic-teleop-twist-keyboard 其中noetic替换成你自己的ros版本 2.使用 roscore #启动roscore rosrun teleop_twist_keyboard teleop_twist_keyboard.py …

零基础STM32单片机编程入门(五)FreeRTOS实时操作系统详解及实战含源码视频

文章目录 一.概要二.什么是实时操作系统三.FreeRTOS的特性四.FreeRTOS的任务详解1.任务函数定义2.任务的创建3.任务的调度原理 五.CubeMX配置一个FreeRTOS例程1.硬件准备2.创建工程3.调试FreeRTOS任务调度 六.CubeMX工程源代码下载七.讲解视频链接地址八.小结 一.概要 FreeRTO…

[SwiftUI 开发] 嵌套的ObservedObject中的更改不会更新UI

1. 发生问题的demo 业务逻辑代码 class Address: ObservableObject {Published var street "123 Apple Street"Published var city "Cupertino" }class User: ObservableObject {Published var name "Tim Cook"Published var address Addr…

nacos漏洞小结

Alibaba Nacos是阿里巴巴推出来的一个新开源项目&#xff0c;是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。致力于帮助发现、配置和管理微服务。Nacos提供了一组简单易用的特性集&#xff0c;可以快速实现动态服务发现、服务配置、服务元数据及流量管理…

64、哥伦比亚大学:CU-Net-目前脑肿瘤分割的最先进模型

本文已被接受发表在2024年IEEE MLISE会议上&#xff08;c&#xff09;2024 IEEE。准确地将脑肿瘤从MRI扫描中分割出来对于制定有效的治疗方案和改善患者预后至关重要。本研究引入了一种新的哥伦比亚大学网络&#xff08;CU-Net&#xff09;架构实现&#xff0c;用于使用BraTS 2…

收银系统源码-千呼新零售2.0【移动管理端】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

如何循环遍历循环中的剩余元素

1、问题背景 给定一段文本&#xff0c;文本中包含多条错误信息&#xff0c;每条错误信息包含行号、错误路径和错误信息。需要从文本中提取出这些错误信息&#xff0c;并以特定的格式输出。 line, Error 12, This is the Error line, Error 34, Another Error line, Error …

【Linux】线程周边002之线程安全

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.Linux线程互斥 1…

每日一题——Python实现PAT乙级1050 螺旋矩阵(举一反三+思想解读+逐步优化)6千字好文

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 时间复杂度分析 空间复杂度分析 总结 我要更强 代码解释 时间复杂度 …

小区服务前台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;住户管理&#xff0c;管理员管理&#xff0c;员工管理&#xff0c;安保管理&#xff0c;安保分配管理&#xff0c;客服聊天管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;公告&#xff0c;…

Mongodb集群中的分布式读写

学习mongodb&#xff0c;体会mongodb的每一个使用细节&#xff0c;欢迎阅读威赞的文章。这是威赞发布的第81篇mongodb技术文章&#xff0c;欢迎浏览本专栏威赞发布的其他文章。如果您认为我的文章对您有帮助或者解决您的问题&#xff0c;欢迎在文章下面点个赞&#xff0c;或者关…

Java进阶学习|Day3.Java集合类(容器),Stream的使用,哈希初接触

java集合类&#xff08;容器&#xff09; Java中的集合类主要由Collection和Map这两个接口派生而出&#xff0c;其中Collection接口又派生出三个子接口&#xff0c;分别是Set、List、Queue。所有的Java集合类&#xff0c;都是Set、List、Queue、Map这四个接口的实现类&#xf…

Powershell 简易爬虫,提取种子网站的磁力链接

目录 一. 需求二. 分析2.1 思路分析2.2 技术点 三. 代码四. 效果 一. 需求 ⏹有网站如下所示&#xff0c;先要求从按照关键词搜索到的网页中&#xff0c;提取出所有的磁力链接。 二. 分析 2.1 思路分析 打开网页之后&#xff0c;从网页中先提取出所有的标题相关的url然后再打…

linux驱动部分内容整理

文章目录 Linux驱动概念应用程序调用驱动程序流程驱动模块的加载linux设备号加载和卸载注册新字符设备注册设备节点自动创建设备节点编译编译驱动程序编译应用程序 地址映射ioctrl命令码的解析 并发与竞争原子操作自旋锁信号量互斥体 linux中断DMA映射其它printkmemcpyvolatile…

RocketMQ常用基本操作

文章中的rabbitmq使用的是rocketmq-all-5.1.3-bin-release版本&#xff0c;需要安装包的可自行下载 RockerMQ启动停止命令 启动命令 nohup sh bin/mqnamesrv & nohup sh bin/mqbroker -n localhost:9876 --enable-proxy & 查看日志 tail -f ~/logs/rocketmqlogs/…

明星中药企业系列洞察(九)一手好牌打的稀烂!近500年老字号锁定退市,太安堂为何“塌房”了?

近日&#xff0c;太安堂发布公告称&#xff0c;公司已收到深交所下发的《关于广东太安堂药业股份有限公司股票终止上市的决定》&#xff0c;深交所决定终止公司股票上市&#xff0c;预计其最后交易日期为7月4日。太安堂曾作为国内知名的中成药上市公司之一&#xff0c;是国家级…

matlab仿真 通信信号和系统分析(上)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第三章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; 一、求离散信号卷积和 主要还是使用卷积函数conv&#xff0c;值得注意的是&#xff0c;得到的卷积和长度结果为81&#xff0…