线程同步与互斥

目录

前言:基于多线程不安全并行抢票

一、线程互斥锁 mutex

1.1 加锁解锁处理多线程并发

 1.2 如何看待锁

1.3 如何理解加锁解锁的本质

1.4 C++RAII方格设计封装锁

前言:基于线程安全的不合理竞争资源

二、线程同步

1.1 线程同步处理抢票

1.2 如何理解"条件变量"

1.3 如何理解条件变量函数需要传锁参数


前言:基于多线程不安全并行抢票

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#define NUM 10
using namespace std;
int global_ticket = 10000;void* GetTicket(void* args)
{char* pc =(char*)args;while(global_ticket > 0){usleep(123);cout << pc <<" ticket= " << global_ticket--<<endl;}delete pc;
}int main()
{for(int i=0;i<NUM;i++){char* pc = new char[64];pthread_t tid;snprintf(pc,128,"thread %d get a ticket",i+1);pthread_create(&tid,nullptr,GetTicket,pc);}while(true);return 0;
}

发现结果和我们代码的预想不一样,出现了票已经售完却仍抢票的现象!

为什么会出现这种现象? 原来Linux线程是轻量级进程,是CPU调度的基本单位,所以每个线程在CPU中都有自己的时间片,所以线程会在CPU上不断的切换,这样有可能某个线程在运行函数过程中突然被截胡了!然后再轮到它的时候继续运行!那结合本次抢票逻辑,可能某个线程首先while条件满足了拥有了抢票资格,但是没有开始抢票就被截胡了,下次回来后它仍拥有资格拿到一张票,可是之前票已经被抢完了!所以他其实没有资格了,但是因为不安全访问全局变量,使得它可以继续拿到票!

这样因为线程的特性原因使得我们无法安全的访问一个全局共享变量!所以我们要使线程互斥访问该变量,当某一个线程访问的时候,其他线程无法干预截胡,该线程必须将自己的代码逻辑执行完或者不执行(要么不做,做就必须完成!) 这就叫线程的互斥!那些代码区域称作为临界区!


一、线程互斥锁 mutex

1.1 加锁解锁处理多线程并发


#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<vector>
using namespace std;int tickets = 10000;class Thread_data
{
public:string name_;pthread_mutex_t* mutex_;
};void* pthread_route(void* args)
{Thread_data* pt = static_cast<Thread_data*>(args);while(true){//加锁pthread_mutex_lock(pt->mutex_);if(tickets > 0){cout << pt->name_<<"get a ticket, tickets = "<<--tickets <<endl;//解锁pthread_mutex_unlock(pt->mutex_);//!!!抢完票后的操作 如果没有这个步骤,该线程会一直占用CPU执行抢票逻辑!usleep(1234);}else{//注意这个逻辑里也要解锁!pthread_mutex_unlock(pt->mutex_);break;}}delete pt;
}#define NUM 5
int main()
{vector<pthread_t> vp(NUM); char buffer[64];//锁初始化//1.锁类型 + 初始化函数//2.全局变量 pthread_mutex_t mutex =宏(PTHREAD_MUTEX_INITIALIZER)pthread_mutex_t mutex;pthread_mutex_init(&mutex,nullptr);for(int i=0;i<NUM;i++){pthread_t tid;snprintf(buffer,sizeof(buffer),"thread %d ",i+1);Thread_data* pt = new Thread_data();pt->mutex_ = &mutex;pt->name_ = buffer;pthread_create(&(vp[i]),nullptr,pthread_route,pt);}for(const auto& tid : vp){pthread_join(tid,nullptr);}return 0;
}

现在结果看到不会出现票数为负数的情况,锁保护了我们的共享资源tickets! 


 1.2 如何看待锁

a.锁本身被线程共享,是一个共享资源,锁保护全局变量,锁本身也需要被保护

b.加锁和解锁的过程必须是安全的,也就说加锁的过程必须是原子性的!

c.如果锁申请成功,就继续向后执行,如果不成功则执行流阻塞!

d.谁持有锁,谁进入临界区!

1.3 如何理解加锁解锁的本质

首先理解这个过程我们首先要明确关于CPU的一个知识就是寄存器:

寄存器只有一套,但寄存器被线程所共享,寄存器存储的内容只被当前线程所拥有!

加锁的过程:

① 将内存中的线程变量al的值0放入CPU的寄存器内

② 然后将寄存器内的0与内存变量mutex值交换,其中mutex的为1,这样mutex = 0

③ 最后通过判断al在寄存器的值来判断是否能申请锁成功!>0 成功 else 不成功

此时当某个线程完成了前面两个步骤,其他线程申请锁时将等于0的mutex与al交换,al依旧是0所以他无法申请锁!这样就可以保证只有一个线程申请到锁!

解锁的过程:就是把寄存器al的1存入mutex中即可!

这里swap过程很重要,它是一条汇编完成!其本质是将共享变量放入到我们的上下文中!


1.4 C++RAII方格设计封装锁

//mutex.hpp
#include<iostream>
#include<pthread.h>class Mutex
{
public:Mutex(pthread_mutex_t* lock_p = nullptr):lock_p_(lock_p){}void lock(){if(lock_p_)pthread_mutex_lock(lock_p_);}void unlock(){if(lock_p_)pthread_mutex_unlock(lock_p_);}
private:pthread_mutex_t* lock_p_;
};class LockGuard
{
public:LockGuard(pthread_mutex_t* mutex):mutex_(mutex){mutex_.lock();}~LockGuard(){mutex_.unlock();}
private:Mutex mutex_;
};

💡该封装利用局部类变量的构造析构随着作用域自动调用使得加锁解锁自动调用! 


前言:基于线程安全的不合理竞争资源

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<vector>
#include"mutex.hpp"
using namespace std;int tickets = 10000;class Thread_data
{
public:string name_;pthread_mutex_t* mutex_;
};void* pthread_route(void* args)
{Thread_data* pt = static_cast<Thread_data*>(args);while(true){//加锁//pthread_mutex_lock(pt->mutex_);LockGuard lock(pt->mutex_);if(tickets > 0){cout << pt->name_<<"get a ticket, tickets = "<<--tickets <<endl;//解锁//pthread_mutex_unlock(pt->mutex_);//!!!抢完票后的操作 如果没有这个步骤,该线程会一直占用CPU执行抢票逻辑!}else{//注意这个逻辑里也要解锁!//pthread_mutex_unlock(pt->mutex_);break;}usleep(1000);}delete pt;
}#define NUM 5
int main()
{vector<pthread_t> vp(NUM); char buffer[64];//锁初始化//1.锁类型 + 初始化函数//2.全局变量 pthread_mutex_t mutex =宏(PTHREAD_MUTEX_INITIALIZER)pthread_mutex_t mutex;pthread_mutex_init(&mutex,nullptr);for(int i=0;i<NUM;i++){pthread_t tid;snprintf(buffer,sizeof(buffer),"thread %d ",i+1);Thread_data* pt = new Thread_data();pt->mutex_ = &mutex;pt->name_ = buffer;pthread_create(&(vp[i]),nullptr,pthread_route,pt);}for(const auto& tid : vp){pthread_join(tid,nullptr);}return 0;
}

 可以肯定的是线程资源是安全的了,但是却是一个线程在疯狂抢票,这样的行为不是错误的,但不合理!我们需要每个线程都有机会,所以引入线程同步,利用同步"信号"与等待队列来实现线程公平的竞争!


二、线程同步

1.1 线程同步处理抢票


#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;int tickets = 100;//初始化锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void *pthread_route(void *args)
{string name = (char *)args;while (true){//加锁pthread_mutex_lock(&mutex);//条件等待信号唤醒pthread_cond_wait(&cond, &mutex);if (tickets > 0)cout << name << "get a ticket, tickets = " << --tickets << endl;pthread_mutex_unlock(&mutex);}
}int main()
{pthread_t tid1, tid2, tid3;pthread_create(&tid1, nullptr, pthread_route, (void *)"thread 1 ");pthread_create(&tid2, nullptr, pthread_route, (void *)"thread 2 ");pthread_create(&tid3, nullptr, pthread_route, (void *)"thread 3 ");// 主线程负责每隔一秒信号唤醒条件变量while (true){sleep(1);pthread_cond_signal(&cond);cout << "main wake up a thread ..." << endl;}pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);pthread_join(tid3, nullptr);return 0;
}


 1.2 如何理解"条件变量"

首先明确一下整个代码逻辑:
a.先加锁,保护临界资源

b.条件等待,等待条件满足信号唤醒,否则阻塞!

c.解锁

当线程条件阻塞,会被放入条件队列中等待!当有信号唤醒的时候,从队列中一个一个得出!

1.3 如何理解条件变量函数需要传锁参数

因为等待队列被线程共享,为了保证线程入队列安全,所以需要加锁保护线程安全! 

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

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

相关文章

Python爬虫(十七)_糗事百科案例

糗事百科实例 爬取糗事百科段子&#xff0c;假设页面的URL是: http://www.qiushibaike.com/8hr/page/1 要求&#xff1a; 使用requests获取页面信息&#xff0c;用XPath/re做数据提取获取每个帖子里的用户头像连接、用户姓名、段子内容、点赞次数和评论次数保存到json文件内…

实现不同局域网文件共享的解决方案:使用Python自带HTTP服务和端口映射

文章目录 1. 前言2. 本地文件服务器搭建2.1 python的安装和设置2.2 cpolar的安装和注册 3. 本地文件服务器的发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 数据共享作为和连接作为互联网的基础应用&#xff0c;不仅在商业和办公场景有广泛的应用…

2023.8.26-2023.9.3 周报【3D+GAN+Diffusion基础知识+训练测试】

目录 学习目标 学习内容 学习时间 学习产出 学习目标 1. 3D方向的基础知识 2. 图像生成的基础知识&#xff08;GAN \ Diffusion&#xff09; 3. 训练测试GAN和Diffusion 学习内容 1. 斯坦福cv课程-3D &#xff08;网课含PPT&#xff09; 2. sjtu生成模型课件 3. ge…

TCP--半连接队列和全连接队列

原文地址&#xff1a;https://plantegg.github.io/2020/04/07/%E5%B0%B1%E6%98%AF%E8%A6%81%E4%BD%A0%E6%87%82TCP–%E5%8D%8A%E8%BF%9E%E6%8E%A5%E9%98%9F%E5%88%97%E5%92%8C%E5%85%A8%E8%BF%9E%E6%8E%A5%E9%98%9F%E5%88%97–%E9%98%BF%E9%87%8C%E6%8A%80%E6%9C%AF%E5%85%AC%E…

【OpenCL基础 · 一】因源

文章目录 前言一、单核标量处理器的瓶颈1.提升时钟频率2.提升指令级并行能力 二、多核和向量化1.多核2.向量化 三、异构并行和OpenCL1.GPGPU2.CUDA和OpenCL 前言 随着人工智能的发展以及大部分场景中实时性的要求&#xff0c;人们对于计算机算力要求逐渐增加。为了提高计算速度…

【ES】笔记-Promise基本使用

笔记-基本使用 一、初始Promise1. 抽象表达:2. 具体表达:为什么要用 Promise?promise的基本流程 二、fs读取文件三、AJAX请求四、Promise封装fs模块五、util.promisify方法六、Promise封装AJAX操作 一、初始Promise 1. 抽象表达: 1. Promise 是一门新的技术(ES6 规范) 2. Pr…

短视频矩阵系统接口部署技术搭建

前言 短视频矩阵系统开发涉及到多个领域的技术&#xff0c;包括视频编解码技术、大数据处理技术、音视频传输技术、电子商务及支付技术等。因此&#xff0c;短视频矩阵系统开发人员需要具备扎实的计算机基础知识、出色的编程能力、熟练掌握多种开发工具和框架&#xff0c;并掌握…

C++(17):异常处理

异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理。 异常使得能够将问题的检测与解决过程分离开来&#xff1a;程序的一部分负责检测问题的出现&#xff0c;然后解决该问题的任务传递给程序的另一部分。检测环节无须知道问题处理模块的…

数据结构入门 — 栈

本文属于数据结构专栏文章&#xff0c;适合数据结构入门者学习&#xff0c;涵盖数据结构基础的知识和内容体系&#xff0c;文章在介绍数据结构时会配合上动图演示&#xff0c;方便初学者在学习数据结构时理解和学习&#xff0c;了解数据结构系列专栏点击下方链接。 博客主页&am…

【C++】关于using namepace xxx 使用命名空间和冲突

官方定义 namespace是指 标识符的各种可见范围。命名空间用关键字namespace来定义。 命名空间是C的一种机制&#xff0c;用来把单个标识符下的大量有逻辑联系的程序实体组合到一起。此标识符作为此组群的名字。 基本使用 编译及执行命令&#xff1a; g test.cpp -o test ./…

汽车制造行业,配电柜如何实施监控?

工业领域的生产过程依赖于高效、稳定的电力供应&#xff0c;而配电柜作为电力分配和控制的关键组件&#xff0c;其监控显得尤为重要。 配电柜监控通过实时监测、数据收集和远程控制&#xff0c;为工业企业提供了一种有效管理电能的手段&#xff0c;从而确保生产的连续性、安全性…

Python程序化交易接口批量获取数据源码

小编举例下面是一个简单的示例代码&#xff0c;展示如何使用Python的程序化交易接口批量获取数据&#xff0c;例如开发文档参考&#xff1a;MetaTradeAPI (metatradeapi) - Gitee.com 签名 int Init(); 功能 API 初始化 参数 无 返回值 授权成功的交易账户数量 返回值 &…

实战系列(一)| Dubbo和Spring Cloud的区别,包含代码详解

目录 1. 概述2. 核心功能3. 代码示例4. 适用场景 Dubbo 和 Spring Cloud 都是微服务架构中的重要框架&#xff0c;但它们的定位和关注点不同。Dubbo 是阿里巴巴开源的一个高性能、轻量级的 RPC 框架&#xff0c;主要用于构建微服务之间的服务治理。而 Spring Cloud 是基于 Spri…

学术加油站|基于端到端性能的学习型基数估计器综合测评

编者按 本文系东北大学李俊虎所著&#xff0c;也是「 OceanBase 学术加油站」系列第 11 篇内容。 「李俊虎&#xff1a;东北大学计算机科学与工程学院在读硕士生&#xff0c;课题方向为数据库查询优化&#xff0c;致力于应用 AI 技术改进传统基数估计器&#xff0c;令数据库选…

Kubernetes技术--k8s核心技术持久化存储

有时候需要在集群中进行一些重要的数据进行持久化存储,然后需要的时候再进行挂载,那么下面我们一起来看看如何实现数据的持久化存储操作。 1.nfs网络存储 -1.找一台服务器做nfs的服务端,安装nfs。(这里我们直接在master上实现)。 这里应该找再单独的搭建一个node节点做持…

按钮控件之1---QPushButton 标准按钮/普通按钮控件

1、父类QAbstractButton 2、QPushButton按钮&#xff0c;是Qt常用的控件之一&#xff0c;提供普通的按钮功能。 通过信号槽机制接收触发信号并执行对应动作。3、创建QPushButton 它有三个构造函数&#xff1a; // 空对象 QPushButton(QWidget *parent nullptr); // 指定QPus…

基于Django+node.js+MySQL+杰卡德相似系数智能新闻推荐系统——机器学习算法应用(含Python全部工程源码)+数据集

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境node.js前端环境MySQL数据库 模块实现1. 数据预处理2. 热度值计算3. 相似度计算1&#xff09;新闻分词处理2&#xff09;计算相似度 4. 新闻统计5. API接口开发6. 前端界面实现1&#xff09;运行逻辑2&#xff0…

文心一言 VS CHATGPT

由于近几天来&#xff0c;我的手机短信不断收到百度公司对于“文心一言”大模型的体验邀请&#xff08;真是不胜其烦&#xff09;&#xff01;&#xff01;所以我就抱着试试看的态度点开了文心一言的链接&#xff1a;文心一言 目前看来&#xff0c;有以下两点与chatgpt是有比较…

什么是浏览器缓存(browser caching)?如何使用HTTP头来控制缓存?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 浏览器缓存和HTTP头控制缓存⭐ HTTP头控制缓存1. Cache-Control2. Expires3. Last-Modified 和 If-Modified-Since4. ETag 和 If-None-Match ⭐ 缓存策略⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击…

新方案unity配表工具

工具下载&#xff1a;网盘链接 工具结构&#xff1a;针对每张表格生成一个表格类&#xff0c;其中默认包含一个list和字典类型参数记录表格数据&#xff0c;初始化项目时将list中的数据转为按id索引的dictionary&#xff0c;用于访问数据。额外包含一个同名Temp后缀的类&#…