多线程(线程同步和互斥+线程安全+条件变量)
线程互斥
线程互斥:
任何时刻,保证只有一个执行流进入临界区访问临界资源,通常对临界资源起到保护作用
相关概念
- 临界资源: 一次仅允许一个进程使用的共享资源
- 临界区: 每个线程内部,访问临界资源的代码,就叫做临界区
- 原子性: 不会被任何调度机制打断的操作,该操作只有两态(无中间态,即使被打断,也不会受影响),要么完成,要么未完成
互斥量mutex
概念:
多个线程对一个共享变量进行操控时,会引发数据不一致的问题。此时就引入了互斥量(也叫互斥锁)的概念,来保证共享数据操作的完整性。在被加锁的任一时刻,临界区的代码只能被一个线程访问。
为了更好的阐述这个概念,这里用一个抢票代码去演示
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <string>
#include <unistd.h>
#include <cassert>
#include <pthread.h>
int ticket=1000;
void* getTicket(void* args)
{long id=(long) args;while(1){if(ticket>0){usleep(1000);--ticket;printf("thread %ld get a ticket,the number is %d\n",id,ticket);}else{break;}}
}
int main()
{//创建五个线程pthread_t t1[5];for(int i=0;i<5;++i){pthread_create(&t1[i],nullptr,getTicket,(void*)i);}//主线程在阻塞等待for(int i=0;i<5;++i){pthread_join(t1[i],nullptr);}return 0;
}
运行结果如下:
我们发现票到负数了还会继续执行
原因如下:
- if 语句判断条件为真以后,代码可以并发的切换到其他线程
- usleep 这个过程中,ticket还没有进行
--
的操作有很多线程会进入if条件 - –-ticket 操作本身就不是一个原子操作ticket有三条汇编指令(如下):
movl ticket(%rip), %eax # 把ticket的值(内存)加载到eax寄存器中
subl $1, %eax # 把eax寄存器中的值减1
movl %eax, ticket(%rip) # 把eax寄存器中的值赋给ticket变量
有可能在你执行到第二条汇编的时候,还没来得及拷贝给内存,就别切换走了,就会导致减到负数,因为别的线程在读取的时候内存中的ticket还是1
如何解决上述问题?
- 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
- 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
- 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
在临界区内,线程只能串行执行,在临界区外,线程可以并发执行
互斥量的接口
互斥量其实就是一把锁,是一个类型为pthread_mutex_t
的变量,使用前需要进行初始化操作,使用完之后需要对锁资源进行释放
初始化互斥量:
全局锁或静态锁:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
局部锁:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
参数:
restrict mutex:要初始化的锁
restrict attr:不关心,置空
返回值:
成功返回0,失败返回错误码
注意:
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁再去竞争锁
加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
mutex:要加的锁
返回值:
成功返回0,失败返回错误码
解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
mutex:要解的锁
返回值:
成功返回0,失败返回错误码
销毁互斥量:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
mutex:要销毁锁
返回值:
成功返回0,失败返回错误码
注意:
- 不要销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
- 加锁的粒度要够小
用以上的方法再需要的地方进行加锁
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <string>
#include <unistd.h>
#include <cassert>
#include <pthread.h>
using namespace std;
int ticket=1000;
class ThreadData
{
public:ThreadData(const string& threadname,pthread_mutex_t* mutex):thread_name(threadname),mutex_p(mutex){}string thread_name;pthread_mutex_t* mutex_p;
};
//创建并初始化
//全局的锁这样写可以不用初始化和销毁
// pthread_mutex_t mutex=PTHREAD_ MUTEX_ INITIALIZER;
void* getTicket(void* args)
{ThreadData *td = static_cast<ThreadData *>(args);// ThreadData* td=(ThreadData*) args;while(1){//加锁pthread_mutex_lock(td->mutex_p);if(ticket>0){usleep(1000);cout << td->thread_name << " tickets is " << ticket << endl;--ticket;//解锁pthread_mutex_unlock(td->mutex_p);}else{//解锁pthread_mutex_unlock(td->mutex_p);break;}//抢完票就完了吗?需要形成订单给用户//这里如果不休息,会一直是第4个线程在跑,原因是锁只规定互斥访问,没有规定必须让谁优先执行//锁就是真是的让多个执行流进行竞争的结果usleep(1000);}
}int main()
{//创建五个线程pthread_t t1[5];pthread_mutex_t lock;pthread_mutex_init(&lock,nullptr);for(int i=0;i<5;++i){char buffer[64];snprintf(buffer,sizeof(buffer),"%s""%d","thread ",i+1);//锁用同一把ThreadData* td=new ThreadData(buffer,&lock);pthread_create(&t1[i],nullptr,getTicket,td);}for(int i=0;i<5;++i){pthread_join(t1[i],nullptr);}pthread_mutex_destroy(&lock);return 0;
}
运行结果如下:
这里运行会变慢,因为加锁以后是串行执行!
如何看待锁?
锁本身就是一个共享资源,全局变量是要被保护的,锁用来保护全局资源,锁本身也是全局资源,所以加锁的过程必须是安全的!加锁的过程是**原子的,锁如果申请成功,继续向后执行,**如果暂时没有申请成功,执行流会阻塞
如果锁申请成功,进入临界资源,正在访问临界资源,其他线程正在做什么?
阻塞等待
如果锁申请成功,进入临界资源,正在访问临界资源,我可以被切换吗?
可以,当持有线程的锁被切走,其他线程依旧无法申请锁成功,也无法向后执行,直到我释放这个锁
互斥量的原理
大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性
下面是lock和unlock的伪代码
lock:movb $0, %a1 # 把0值放进寄存器a1里xchgb %a1, mutex # 交换a1寄存器的内容和锁的值(无线程使用锁时,metux的值为1) if (%a1 > 0)return 0; # 得到锁else挂起等待;goto lock;
unlock:movb $1 mutex #把1赋给锁 唤醒等待的线程;return 0;
下图展示了如何实现:
解锁的伪代码步骤(只有有锁的线程才可以执行到这段代码):
- 把mutex的值改为1
- 唤醒等待锁的线程
封装锁
Mutex.hpp
#pragma once#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_);}~Mutex(){}
private:pthread_mutex_t *lock_p_;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex): mutex_(mutex){mutex_.lock(); //在构造函数中进行加锁}~LockGuard(){mutex_.unlock(); //在析构函数中进行解锁}
private:Mutex mutex_;
};
test.cpp
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <memory>
#include <cassert>#include "Mutex.hpp"// 共享资源, 火车票
int tickets = 10000;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *getTicket(void *args)
{long long username = (long long)args;while (true){{//出了作用域会销毁LockGuard lockguard(&lock); // RAII风格的加锁!if (tickets > 0){usleep(1254); std::cout << username << " 正在进行抢票: " << tickets << std::endl;tickets--;}else{break;}}usleep(1000); }return nullptr;
}
int main()
{#define NUM 4pthread_t t1[5];for(int i=0;i<5;++i){pthread_create(&t1[i],nullptr,getTicket,(void*)i);}//主线程保持运行for(int i=0;i<5;++i){pthread_join(t1[i],nullptr);}return 0;
}
线程安全和可重入
概念
线程安全: 多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
重入: 同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。
常见的线程安全的情况
- 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
- 类或者接口对于线程来说都是原子操作
- 多个线程之间的切换不会导致该接口的执行结果存在二义性
常见的线程不安全的情况
- 不保护共享变量的函数
- 函数状态随着被调用,状态发生变化的函数
- 返回指向静态变量指针的函数
- 调用线程不安全函数的函数
常见可重入的情况
- 不使用全局变量或静态变量
- 不使用用malloc或者new开辟出的空间
- 不调用不可重入函数
- 不返回静态或全局数据,所有数据都有函数的调用者提供
- 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
常见不可重入的情况
- 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
- 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
- 可重入函数体内使用了静态的数据结构
区别与联系
区别:
- 函数是可重入的,那就是线程安全的
- 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
- 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的
联系:
- 可重入函数是线程安全函数的一种
- 线程安全不一定是可重入的(不一定发生线程安全问题),而可重入函数则一定是线程安全的。
- 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。
死锁
概念
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁产生的四个必要条件:
- 互斥条件:一个资源每次只能被一个执行流使用
- 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
所谓的必要条件是都要满足才能形成死锁,只要有一个不满足就不是死锁
避免死锁
- 破坏死锁的四个条件(上面分别对应的是:1.不使用锁 2.让一个执行流放开资源 3. 让一个个执行流剥夺一个执行流的资源 4. 调整申请资源的顺序)
- 假设顺序要一致
- 避免锁未释放的场景
- 资源一次性分配
避免死锁算法:
- 银行家算法:为了防止银行家资金无法周转而倒闭,对每一笔贷款,必须考察其是否能限期归还。在操作系统中研究资源分配策略时也有类似问题,系统中有限的资源要供多个进程使用,必须保证得到的资源的进程能在有限的时间内归还资源,以供其他进程使用资源。
- 死锁检测法
相关文章:
多线程(线程同步和互斥+线程安全+条件变量)
线程互斥 线程互斥: 任何时刻,保证只有一个执行流进入临界区访问临界资源,通常对临界资源起到保护作用 相关概念 临界资源: 一次仅允许一个进程使用的共享资源临界区: 每个线程内部,访问临界资源的代码&am…...
Flutter学习——开发Flutter需要的技能
第二章 Flutter开发所需要掌握的知识 文章目录 第二章 Flutter开发所需要掌握的知识前言一、开发语言Dart语言Android/Ios知识 二、组件学习三、调试与性能优化总结 前言 上一章,介绍了Flutter的来源和平台支持及特点,这一章,来梳理一下学习…...
SPSS如何进行因子分析和主成分分析之案例实训?
文章目录 0.引言1.因子分析2.主成分分析 0.引言 因科研等多场景需要进行数据统计分析,笔者对SPSS进行了学习,本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记总结,本文对因子分析和主成分分析进行阐述。 1.因…...
图标字体与HTML转义字符:网页设计中的两个关键概念
在网页设计中,图标字体和HTML转义字符是两个重要的概念。图标字体用于显示网页的图标,可以让用户更加直观地理解网页的内容。而HTML转义字符则用于在网页中插入特殊的字符,以保证网页的安全性和可读性。 一、图标字体 在网页中显示图标&#…...
Elasticsearch详解
文章目录 概览使用与ES交互索引创建索引查询索引删除文档创建修改文档局部修改文档查询文档删除全查询 整合SpringBootpom依赖application.ymlElasticsearchAutoConfigurationElasticsearchPropertiesElasticsearchConstantPersonSearchPageHelperPersonServiceBaseElasticsear…...
学习笔记(13)网络基础
目录 1,get与post的区别2,JSON解析2.1,JSON.stringify2.2,JSON.parse 3,cookie3.1,set方法3.2,cookie方法用于设置响应头, 4,http模块4.1,请求报文和响应报文…...
LeertCode 134 加油站
题目: 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。给定两个整数数组 …...
python文件操作的基本流程
引入 程序运行过程中产生的数据会保存到内存中,如果想要永久保存下来,就必须将数据存放在硬盘上,应用程序如果想要操作计算机的硬件就必须通过操作系统,文件就是操作系统提供给应用程序来操作硬盘的虚拟概念,应用程序…...
1. 两数之和
原题链接: 1. 两数之和 https://leetcode.cn/problems/two-sum/ 完成情况: ##1. n 2 n^2 n2复杂度 2.HashMap进行优化 3.空间换时间方法 即,构建一个 1 0 − 9 10^-9 10−9 到 1 0 9 10^9 109这个大的数组,然后把数填进去&…...
操作系统:06 进程通信
1 基本概念 进程间通信是指两个或多个进程之间交互数据的过程,因为进程之间是相互独立的,为了协同工作必须进行进程间交互数据 2 进程间通信的分类 2.1 简单的进程间通信: 信号(携带附加数据)、文件、命令行参数、环境变量表 2.2 传统的进…...
WRF模式
随着生态文明建设和“碳中和”战略的持续推进,我国及全球气候变化及应对是政府、科学界及商业界关注的焦点。气候是多个领域(生态、水资源、风资源及碳中和等问题)的主要驱动因素,合理认知气候变化有利于解释生态环境变化机理及过…...
2直接连接的网络与VLAN划分【实验】【计算机网络】
2直接连接的网络与VLAN划分【实验】【计算机网络】 前言推荐2直接连接的网络与VLAN划分2.1共享式以太网和交换式以太网实验目的实验内容及实验环境实验原理共享式以太网交换式以太网 实验过程搭建实验环境初始化序训练操作共享式以太网-操作交换式以太网查看共享式以太网冲突查…...
【Linux0.11代码分析】04 之 head.s 启动流程
【Linux0.11代码分析】04 之 head.s 启动流程 一、boot/head.s 系列文章如下: 系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》 . 1.《【Linux0.11代码分析】01 之 代码目录分析》 2.《【Linux0.11代码分析】02 之…...
自动化测试和selenium的使用
目录 自动化测试定义 为什么选择selenium来作为我们web自动化测试的工具? 自动化测试定位元素 使用cssSelector定位 使用XPath 定位 操作测试对象 模拟手动从键盘输入 点击对象 获取页面文本 清除对象输入的文本内容 添加等待(三种方式&#…...
Ubuntu常用终端操作
终端快捷键 打开 Ctrlaltt:打开终端(默认路径为家目录) Ctrlshiftn:打开终端(与当前终端处于同一路径下) Ctrlshiftt:打开终端(在大终端下面创建小终端) alt数字 关闭 exitCtrld 窗口切换 …...
Spring Security 6.x 系列【34】认证篇之前后端分离场景下的集成方案
有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.0.4 本系列Spring Security 版本 6.0.2 源码地址:https://gitee.com/pearl-organization/study-spring-security-demo 文章目录 1. 前言2. 案例演示2.1 未认证2.2 认证成功2.3 认证失败2.4 权限不足2.5 注…...
Qt之QTextToSpeech 让你的应用程序说话
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言QTextToSpeech基础使用1.创建一个QTextToSpeech对象2.朗读文字3.朗读文件和状态信息4.设置QTTS(QTextToSpeech)属性5.输出支持区域的设置列表、语言6.实现小数点朗读QTextToSpeech项目(练习)…...
为什么程序员喜欢用Linux?
Linux哪些行业在运用? Linux系统运用极其广泛,不少用户只知道windows,是因为,Linux的运用主要是在企业端。现在科技极其发达,我们手机在手,就能干很多事情,只需点一点屏幕,轻松完成…...
leetcode 598. 范围求和 II
题目描述解题思路执行结果 leetcode 598. 范围求和 II 题目描述 范围求和 II 给你一个 m x n 的矩阵 M ,初始化时所有的 0 和一个操作数组 op ,其中 ops[i] [ai, bi] 意味着当所有的 0 < x < ai 和 0 < y < bi 时, M[x][y] 应该…...
javaweb前置知识
1.CSS CSS的角色:页面显示的美观风格CSS的基础语法:标签样式;类样式;ID样式;组合样式;嵌入式样式表;内部样式表;外部样式表盒子模型:border、margin、padding定位和浮动…...
基于微信小程序的酒店预定管理系统设计与实现
第1章 绪论 1 1.1开发背景与意义 1 1.2开发方法 1 1.3论文结构 1 2系统开发技术与环境 3 2.1 系统开发语言 3 2.2 系统开发工具 3 2.3 系统页面技术 3 2.4 系统数据库的选择 4 2.5 系统的运行环境 4 2.5.1 硬件环境 4 2.5.2 软件环境 4 3系统分析 5 3.1可行性分析 5 3.1.1 经济…...
26. Service——深入学习
本章讲解知识点 Service 会话保持机制Service 的多端口设置Service 支持的网络协议Kubernetes 的服务发现机制Headless ServiceEndpoint Slices这一节我们来讲讲 Service 更多细节 1. Service 会话保持机制 Service 支持通过设置 sessionAffinity 实现基于客户端 IP 的会话保…...
【算法】Check If Word Is Valid After Substitutions 检查替换后的词是否有效
文章目录 Check If Word Is Valid After Substitutions 检查替换后的词是否有效问题描述:分析代码 Tag Check If Word Is Valid After Substitutions 检查替换后的词是否有效 问题描述: 给你一个字符串 s ,请你判断它是否 有效 。 字符串 s…...
基于jenkinsfile布置java工程
需求 通过jenkins发布java项目到服务器 预备环境 项目地址: https://gitee.com/asaland/sb-docker-appJenkins 2.387.3 通过Jenkinsfile实现方式 jenkins ui 配置pipeline 什么是pipeline? 直接看注释吧,简单点就是编排可以多个跨时间的构建代理…...
Spring JpaTransactionManager事务管理
首先,在做关于JpaTransactionManager之前,先对Jpa做一个简单的了解,他毕竟不如hibernate那么热门,其实二者很相识,只不过后期hibernate和JDO 版本都已经兼容了其Jpa,目前大家用的少了。 JPA全称Java Persi…...
全国职业院校技能大赛网络建设与运维赛项赛题(七)
全国职业院校技能大赛 网络建设与运维 赛题 (七)...
asp.net+sqlserver企业公司进销存管理系统
基于WEB的进销存管理系统主要企业内部提供服务,系统分为管理员,和员工2部分。 在本基于WEB的进销存管理系统中分为管理员,和普通用户2中模式,其中管理人员主要是对企业内商品类型。商品信息商品的出入库信息,以及员工…...
WxGL应用实例:绘制点云
WxGL附带了几个工具函数,其中read_pcfile用来解析.ply和.pcd格式的点云文件,该函数返回一个PointCloudData类实例,包含以下属性: PointCloudData.ok - 数据是否可用,布尔型PointCloudData.info - 数据可用性说明&…...
一个月内面了30家公司,薪资从18K变成28K,真行啊····
工作3年,换了好几份工作(行业流行性大),每次工作都是裸辞。朋友都觉得不可思议。因为我一直对自己很有信心,而且特别不喜欢请假面试,对自己负责也对公司负责。 但是这次没想到市场环境非常不好,…...
《计算机网络——自顶向下方法》精炼——1.4到1.7
三更灯火五更鸡,努力学习永不止。无惧困难与挑战,砥砺前行向成功。 文章目录 引言正文时延排队时延 吞吐量协议层次,服务模型(重点)封装(重点)网络安全(选看)恶意软件的分…...
杭州做网站设计公司/优化器
语音合成:也被称为文本转换技术(TTS),它是将计算机自己产生的、或外部输入的文字信息转变为可以听得懂的、流利的口语输出的技术。 刚开始项目需求订单实时播报,最初采用的H5的语音合成Speech Synthesis APIÿ…...
自己建网站用gbk/东莞专业网站推广工具
本来是一件很简单的事情。cvsup -gL 2 -h cvsup.freebsd.org /usr/share/examples/cvsup/ports-supfilecd /usr/ports/lang/php5(或者用:/usr/ports/www/mod_php5)make install clean但是结果却发现pivot无法运行,报一个错误,说是什么函数找不…...
北京网站建设app/国外域名注册平台
内容来源于悟空问答"如何看待程序员在火车站候车室写代码画面曝光?"作者:大学生编程指南原文可点击阅读全文前去围观。10月2日,有网友曝光了某程序员在火车站候车室写代码的画面,网友评价这是程序员的悲哀。大过节的,许…...
有没有做面粉美食的网站/谷歌搜索入口365
在使用 Mybatis 的时候,都会使用resultMap节点来绑定列与bean属性的对应关系,但是一般就只会使用其简单的属性,他还有一些比较复杂的属性可以实现一些高级的功能,在没查看源码之前,我也只会简单的使用,很多…...
网站数据库管理系统/免费b站推广入口
当多个数组有关时 比如今年暑假不AC中的测试案例,可以通过构建结构体来进行排序 这是模板: #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<queue> using namespace st…...
php+ajax网站开发典型实例pdf/网络营销是什么专业
http://blog.csdn.net/fakine/article/details/7517417/ 1. 使用库函数 string.h strstr函数 函数名: strstr 功 能: 在串中查找指定字符串的第一次出现 用 法: char *strstr(char *str1, char *str2); 说明:返回指向第一次出现str2位置的指针,如果…...