Linux——多线程(二)

在上一篇博客中我们已经介绍到了线程控制以及对应的函数调用接口,接下来要讲的是真正的多线程,线程安全、线程互斥、同步以及锁。

一、多线程

简单写个多线程的创建、等待的代码 

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<vector>
using namespace std;const int threadnum = 5;
void *handlerTask(void *args)
{string name = static_cast<char*>(args);while(true){sleep(1);cout<<"I am "<<name<<endl;}return nullptr;
}int main()
{vector<pthread_t> threads;for(int i=0;i<threadnum;i++){char threadname[64];snprintf(threadname,64,"thread-%d",i+1);pthread_t tid;sleep(1);pthread_create(&tid,nullptr,handlerTask,threadname);//多线程创建threads.push_back(tid);}for(auto &tid:threads){pthread_join(tid,nullptr);//等待}return 0;
}

 

用一下之前的接口实现多线程的抢票功能

thread.hpp 

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<functional>
#include<pthread.h>namespace Thread_Module
{template <typename T>using func_t = std::function<void(T&)>;// typedef std::function<void(const T&)> func_t;template <typename T>class Thread{public:void Excute(){_func(_data);}public:Thread(func_t<T> func,T &data,const std::string &threadname = "none"):_threadname(threadname),_func(func),_data(data){}static void* threadrun(void *args)//线程函数{Thread<T> *self = static_cast <Thread<T>*>(args);self->Excute();return nullptr;}bool Start()//线程启动!{int n = pthread_create(&_tid,nullptr,threadrun,this);if(!n)//返回0说明创建成功{_stop = false;//说明线程正常运行return true;}else{return false;}}void Stop(){_stop = true;}void Detach()//线程分离{if(!_stop){pthread_detach(_tid);}}void Join()//线程等待{if(!_stop){pthread_join(_tid,nullptr);}}std::string threadname()//返回线程名字{return _threadname;}~Thread(){}private:pthread_t _tid;//线程tidstd::string  _threadname;//线程名T &_data;//数据func_t<T> _func;//线程函数bool _stop; //判断线程是否停止 为true(1)停止,为false(0)正常运行};
}

 main.cc

#include <iostream>
#include <mutex>
#include "Thread.hpp"
#include<string>
#include<vector>using namespace Thread_Module;
int g_tickets = 10000; // 共享资源,没有保护的void route(int &tickets)
{while (true){if(tickets>0){usleep(1000);printf("get tickets: %d\n", tickets);tickets--;}else{break;}}
}const int num = 4;
int main()
{// std::cout << "main: &tickets: " << &g_tickets << std::endl;std::vector<Thread<int>> threads;// 1. 创建一批线程for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1);threads.emplace_back(route, g_tickets, name);}// 2. 启动 一批线程for (auto &thread : threads){thread.Start();}// 3. 等待一批线程for (auto &thread : threads){thread.Join();std::cout << "wait thread done, thread is: " << thread.threadname() << std::endl;}return 0;
}

运行之后,可以看出抢多了票,票数怎么能抢到负数了

原因是发生了线程不安全问题

二、线程安全

抢票抢到负数造成的票数不一致本质是g_tickets是全局变量(无保护),对全局的g_tickets的判断不是原子的

为什么抢到负数?

可能是tickets已经等于1了,但是判断的时候,让多个线程进入抢票逻辑了(涉及到CPU调度),进入抢票逻辑后还执行了ticket--(等价于tickets = tickets - 1)

tickets--的本质:

  • 从内存读到CPU
  • CPU内部进行--操作
  • 数据写回内存

 tickets--操作不是原子的-----又引出一个问题------>什么是原子的?

当一条语句转成汇编代码只有一条汇编是,那么就可以说这条语句是原子的

那么如何解决这种数据不一致问题呢?

导致数据不一致的原因:共享资源没有被保护,多线程对该资源进行了交叉访问

而解决办法就是:对共享资源进行加锁

三、线程互斥(锁)

再引入几个概念

临界资源:多个执行流进行安全访问的共享资源 

临界区:访问临界资源的代码就叫做临界区

线程互斥:让多个线程串行访问共享资源,任何时候只能有一个执行流在访问共享资源

加锁的本质就是把并行执行转成串行执行

原子性:对一个资源进行访问的时候要么就不做,要么就做完

当一条语句转成汇编代码只有一条汇编是,那么就可以说这条语句是原子的

3.1加锁保护 

锁的相关系统调用:

锁的全局(静态)初始化:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

man pthread_mutex_init(初始化锁)

 参数:

pthread_mutex_t *mutex:                                创建的互斥锁指针

const pthread_mutexattr_t *mutexattr:            锁的属性,一般设为nullptr

返回值:                                                          初始化成功返回0,失败返回错误码

man pthread_mutex_destroy(锁的销毁)

参数:

pthread_mutex_t *mutex:                                创建的互斥锁指针

返回值:                                                          销毁成功返回0,失败返回错误码

 man pthread_mutex_lock(加锁):

参数:

 pthread_mutex_t *mutex:                                互斥锁指针

返回值:                                                           加锁成功返回0,失败返回错误码

1

申请锁成功:函数就会返回,允许继续向后运行

申请锁失败:函数就会阻塞

函数调用失败:出错返回

  man pthread_mutex_unlock(解锁):

 

参数:

 pthread_mutex_t *mutex:                                还是互斥锁指针

返回值:                                                           解锁成功返回0,失败返回错误码

一般都是这么用的

//初始化
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,nullptr);//加锁
pthread_mutex_lock(&mutex);
//临界区——访问临界资源的代码写这里
//。。。
//访问完了解锁pthread_mutex_unlock(&mutex);

 3.2解决数据不一致问题

有了以上的系统调用我们对抢票代码进行修改,对其进行加锁

.hpp 

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<functional>
#include<pthread.h>namespace Thread_Module
{template <typename T>using func_t = std::function<void(T)>;// typedef std::function<void(const T&)> func_t;template <typename T>class Thread{public:void Excute(){_func(_data);}public:Thread(func_t<T> func,T data,const std::string &threadname = "none"):_threadname(threadname),_func(func),_data(data){}static void* threadrun(void *args)//线程函数{Thread<T> *self = static_cast <Thread<T>*>(args);self->Excute();return nullptr;}bool Start()//线程启动!{int n = pthread_create(&_tid,nullptr,threadrun,this);if(!n)//返回0说明创建成功{_stop = false;//说明线程正常运行return true;}else{return false;}}void Stop(){_stop = true;}void Detach()//线程分离{if(!_stop){pthread_detach(_tid);}}void Join()//线程等待{if(!_stop){pthread_join(_tid,nullptr);}}std::string threadname()//返回线程名字{return _threadname;}~Thread(){}private:pthread_t _tid;//线程tidstd::string  _threadname;//线程名T _data;//数据func_t<T> _func;//线程函数bool _stop; //判断线程是否停止 为true(1)停止,为false(0)正常运行};
}

.cc


class ThreadData
{
public:ThreadData(int &tickets,const std::string &name,pthread_mutex_t &mutex):_tickets(tickets),_name(name),_total(0),_mutex(mutex){}~ThreadData(){}
public:int &_tickets;//总票数的引用std::string _name;int _total;//线程单独抢的票数//std::mutex &_mutex;pthread_mutex_t &_mutex;
};
int g_tickets = 10000;//总票数
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;void scramble(ThreadData *td)//抢票
{while(true){//pthread_mutex_lock(&gmutex);pthread_mutex_lock(&td->_mutex);if(td->_tickets > 0){usleep(1000);std::cout<<"线程:"<<td->_name.c_str()<<"正在抢票中..."<<" 抢到的票数是:"<<td->_total<<std::endl;td->_tickets--;// pthread_mutex_unlock(&gmutex);pthread_mutex_unlock(&(td->_mutex));td->_total++;}else{pthread_mutex_unlock(&(td->_mutex));//pthread_mutex_unlock(&gmutex);break;}}
}
const int num = 3; int main()
{pthread_mutex_t mutex;pthread_mutex_init(&mutex,nullptr);std::vector<Thread<ThreadData*>> threads;std::vector<ThreadData *> datas;//创建一些线程for(int i=0;i<num;i++){std::string name = "thread - "+std::to_string(i+1);ThreadData *td = new ThreadData(g_tickets,name,mutex);threads.emplace_back(scramble,td,name);datas.emplace_back(td);}//启动全部线程for(auto &thread:threads){thread.Start();}//等待线程for(auto &thread:threads){thread.Join();}for(auto data:datas){std::cout<<"线程名字:"<<data->_name<<"以及此线程总共抢的票数:"<<data->_total<<std::endl;delete data;    }pthread_mutex_destroy(&mutex);return 0;
}

此时票数正好10000,结果正常

需要注意的是:

当一个线程从临界区出来并且释放锁后,执行后续任务这段时间,其它线程才有更大的概率去竞争锁

加锁时,要保证临界区的粒度非常小,那些不是必须放临界区内的代码就别放里面

3.3互斥

锁的封装

#include<iostream>
#include<pthread.h>class LockGuard
{
public:LockGuard( pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGuard(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t *_mutex;
}

线程互斥:让多个线程以串行方式访问一段临界区代码 

之前使用锁的代码,这个锁必须被所有线程所看见,所以对于锁本身而言也是一个共享资源,那么谁来保证锁的安全性呢?通过加锁解锁都是原子的来保证锁自身的安全性 

互斥到底是啥?

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码改一下

交换(xchgb)的本质:不是拷贝到寄存器,而是所有线程在竞争锁的时候只有一个1(这个1就是锁)

加锁过程中,xchgb是原子的,可以保证锁的安全

锁只能被一个线程所持有,而且由于xchgb汇编只有一条指令,即使申请锁的过程时CPU进行调度线程被切换也不影响。一旦一个线程通过交换那到了锁,它走的时候也就把锁带走了,其它线程只能等他用完把锁还回来。

 

CPU寄存器硬件只有一套,但是CPU寄存器内部的数据,就是线程的硬件上下文

数据在内存中,所有线程都能访问,属于共享的;但是如果转移到CPU内部寄存器中,就属于一个线程独有了

mutex简单理解就是一个0/1的计数器,用于标记资源访问状态:

  • 0表示已经有执行流加锁成功,资源处于不可访问,
  • 1表示未加锁,资源可访问。

但是还有另外的问题:加锁,线程竞争锁是自由竞争的,竞争能力强的线程会导致其他线程抢不到锁,访问不了临界资源导致其他线程一直阻塞,造成其它线程的饥饿问题

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

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

相关文章

【C++】list的使用(下)

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; STL || C 目录 前言&#x1f525;操作list对象的接口函数&#xff08;opeartions&#xff09;spliceremoveremove_ifuniquemergesortreverse 结语 前言 本篇博客主要内容&#xff1a;STL…

MySQL 自定义函数(实验报告)

一、实验名称&#xff1a; 自定义函数 二、实验日期&#xff1a; 2024年 6 月 1 日 三、实验目的&#xff1a; 掌握MySQL自定义函数的创建及调用&#xff1b; 四、实验用的仪器和材料&#xff1a; 硬件&#xff1a;PC电脑一台&#xff1b; 配置&#xff1a;内存&#…

如何区分解析亚马逊网站产品搜索结果页HTM代码中广告位( Sponsored)和自然位的产品ASIN及排名

在开发亚马逊产品广告排名插件的时候需要通过页面HTML代码分别找出属于广告位和自然搜索结果的产品ASIN及排名&#xff0c;所以需要找到区分广告位和自然搜索结果的HTML代码属性&#xff1a; 所有搜索结果页的产品不管是广告位还是自然位&#xff0c;都包括在 标签里&#xff…

RTPS协议之Behavior Module

目录 交互要求基本要求RTPS Writer 行为RTPS Reader行为 RTPS协议的实现与Reader匹配的Writer的行为涉及到的类型RTPS Writer实现RTPS WriterRTPS StatelessWriterRTPS ReaderLocatorRTPS StatefulWriterRTPS ReaderProxyRTPS ChangeForReader RTPS StatelessWriter BehaviorBe…

ARM-V9 RME(Realm Management Extension)系统架构之系统安全能力的信任根服务

安全之安全(security)博客目录导读 目录 一、信任根服务 1、非易失性存储 2、根看门狗 3、随机数生成器 4、加密服务 5、硬件强制安全性 本节定义了系统架构必须支持的一般安全属性和能力&#xff0c;以确保RME安全性。 本章扩展了可能属于系统认证配置文件的一部分的其…

30 分钟内掌握 Mainnet、Testnet 和 Devnet。Devnet是什么??

在区块链技术领域&#xff0c;Mainnet、Testnet 和 Devnet 等术语经常被使用&#xff0c;但也经常被误解。 这三种环境在区块链应用的开发和部署中起着至关重要的作用&#xff0c;但它们的区别和目的却常常被混淆。 让我们踏上探索之旅&#xff0c;揭开 Mainnet、Testnet 和 De…

Simulink中使用ROS1自定义消息

Simulink中使用ROS1自定义消息 简介前提条件操作流程问题一问题二问题三 吐槽 简介 最近在做的项目里需要使用Simulink与ROS联合仿真&#xff0c;这里就遇到了一个问题&#xff0c;Simulink无法直接使用ROS中的自定义消息&#xff0c;需要在MATLAB中生成一下&#xff0c;再引入…

GiantPandaCV | FasterTransformer Decoding 源码分析(六)-CrossAttention介绍

本文来源公众号“GiantPandaCV”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;FasterTransformer Decoding 源码分析(六)-CrossAttention介绍 GiantPandaCV | FasterTransformer Decoding 源码分析(一)-整体框架介绍-CSDN博客 …

MyBatis系统学习篇 - 分页插件

MyBatis是一个非常流行的Java持久层框架&#xff0c;它简化了数据库操作的代码。分页是数据库查询中常见的需求&#xff0c;MyBatis本身并不直接支持分页功能&#xff0c;但可以通过插件来实现&#xff0c;从而帮助我们在查询数据库的时候更加方便快捷 引入依赖 <dependen…

移动端路由切换解决方案 —— 虚拟任务栈让你的 H5 像APP一样丝滑

目录 01: 前言 02: 通用组件&#xff1a;trigger-menu 和 trigger-menu-item 构建方案分析 03: 通用组件&#xff1a;构建 trigger-menu 和 trigger-menu-item 04: 前台业务下 H5 的应用场景 05: 通用组件&#xff1a;transition-router-view 构建方案分析 与 虚拟任务栈…

Java实战:将学生列表写入文件

本实战项目旨在演示如何使用Java语言将学生信息列表写入到一个文本文件中&#xff0c;并进行单元测试以确保代码的正确性。 创建静态方法 定义一个名为writeStudentsToFile的静态方法&#xff0c;该方法接收两个参数&#xff1a;一个Student对象的列表和一个文件路径。使用File…

Python疑难杂症--考试复习

1.排序输出字典中数据 dic1 {Tom:21,Bob:18,Jack:23,Ana:20} dic2 {李雷:21,韩梅梅:18,小明:23,小红:20} nint(input()) if n>len(dic1):nlen(dic1) print(sorted(dic1.keys())[:n]) print(sorted(dic2.items(),keylambda item:item[1])[:n]) 2.罗马数字转换 def F(s):d{…

SQL—DQL(数据查询语言)之小结

一、引言 在前面我们已经学习完了所有的关于DQL&#xff08;数据查询语言&#xff09;的基础语法块部分&#xff0c;现在对DQL语句所涉及的语法&#xff0c;以及需要注意的事项做一个简单的总结。 二、DQL语句 1、基础查询 注意&#xff1a; 基础查询的语法是&#xff1a;SELE…

FineBi导出Excel后台版实现

就是不通过浏览器,在后台运行的导出 参考文档在:仪表板查看接口- FineBI帮助文档 FineBI帮助文档 我这里是将这个帮助文档中导出的excel文件写到服务器某个地方后,对excel进行其他操作后再下载。由于原有接口耦合了HttpServletRequest req, HttpServletResponse res对象,…

海外短剧APP/H5 系统开发搭建

目前已经有多个客户用我们搭建的海外短剧系统&#xff0c;在使用中已经取得了较高的收益。目前一个客户打算做日本区域的海外短剧项目&#xff0c;需求已经理清楚了&#xff0c;系统正在搭建中

[MYSQL] 部门工资最高的员工

表&#xff1a; Employee ----------------------- | 列名 | 类型 | ----------------------- | id | int | | name | varchar | | salary | int | | departmentId | int | ----------------------- 在 SQL 中&#xff0c;id…

Deconfounding Duration Bias in Watch-time Prediction for Video Recommendation

Abstract 观看时间预测仍然是通过视频推荐加强用户粘性的关键因素。然而&#xff0c;观看时间的预测不仅取决于用户与视频的匹配&#xff0c;而且经常被视频本身的持续时间所误导。为了提高观看时间&#xff0c;推荐总是偏向于长时间的视频。在这种不平衡的数据上训练的模型面…

[机器学习]GPT LoRA 大模型微调,生成猫耳娘

往期热门专栏回顾 专栏描述Java项目实战介绍Java组件安装、使用&#xff1b;手写框架等Aws服务器实战Aws Linux服务器上操作nginx、git、JDK、VueJava微服务实战Java 微服务实战&#xff0c;Spring Cloud Netflix套件、Spring Cloud Alibaba套件、Seata、gateway、shadingjdbc…

牛客网刷题 | BC104 翻转金字塔图案

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 KiKi学习了循环&am…

万字详解 MySQL MGR 高可用集群搭建

文章目录 1、MGR 前置介绍1.1、什么是 MGR1.2、MGR 优点1.3、MGR 缺点1.4、MGR 适用场景 2、MySQL MGR 搭建流程2.1、环境准备2.2、搭建流程2.2.1、配置系统环境2.2.2、安装 MySQL2.2.3、配置启动 MySQL2.2.4、修改密码、设置主从同步2.2.5、安装 MGR 插件 3、MySQL MGR 故障转…