<Linux> 多线程

文章目录

  • 线程
  • 线程互斥
    • 死锁
  • 线程同步
  • 生产者消费者模型
  • POSIX信号量
    • 基于环形队列的生产消费模型
  • 线程池

线程

线程是进程内部可以独立运行的最小单位

进程是资源分配的基本单位,线程是调度器调度的基本单位

线程在进程的地址空间内运行

进程内的大部分资源线程是共享的,但也有属于线程自己的独立资源,主要是寄存器和栈(位于共享区)

为什么引入线程?

创建新线程的工作要比创建新进程的工作少得多

线程的切换要比进程的切换所作的工作少得多

线程异常

单个线程出现崩溃会导致整个进程奔溃,因为线程是在进程内部运行的

Linux下的线程

Linux将线程视为一种特殊类型的进程,称作轻量级进程(Lightweight Process,LWP)

Linux并没有提供系统调用来对线程进行操作,对线程操作使用的是第三方库

来段代码感受一下

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *routine(void *args)
{while (true){printf("sub process running ...  pid:%d\n", getpid());sleep(1);}
}int main()
{pthread_t t;pthread_create(&t, nullptr, routine, nullptr);while (true){printf("main process running ...  pid:%d\n", getpid());sleep(3);}pthread_join(t, nullptr);return 0;
}

运行结果如下

请添加图片描述

程序运行起来之后检测可以看到,有两个线程,其中一个pid和lwp相等,这就是主线程,下面的就是新线程

在这里插入图片描述

每个进程内部至少有一个线程(主线程)

线程互斥

多个线程在对同一份资源进行访问时,很可能会造成数据紊乱的问题,来看个例子

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;int num = 10000;void *routine(void *args)
{while (true){if (num > 0){usleep(100);printf("%d\n", num);num--;}else{break;}usleep(1000);}}int main()
{const int n = 100;pthread_t t[n];for (size_t i = 0; i < n; i++){pthread_create(t + i, nullptr, routine, nullptr);}for (size_t i = 0; i < n; i++){pthread_join(t[i], nullptr);}return 0;
}

创建两个新线程,让这两个线程对同一个数字进行争夺,类似于抢票,没有票就退出

只看最后的几个结果

请添加图片描述

这里n==0时明显不能进入if语句,但是后面居然打印出负数
这就是多线程对于同一份资源在进行访问时造成的数据紊乱

临界区

临界区是指一段代码,当一个线程(或进程)进入这段代码并开始执行时,其他线程(或进程)不能同时进入执行该段代码的区域。这是为了确保共享资源在同一时间只能被一个线程访问,避免数据竞争和不一致的状态。

临界资源

临界资源是指在多线程或多进程环境中被共享访问的数据、对象或资源。因为多个线程或进程可能同时访问这些资源,所以需要在访问它们时确保数据的一致性和正确性。

如何对临界资源进行保护?------‘锁’

锁的使用可以确保当一个线程在访问共享资源时,其他线程无法同时访问该资源,从而保证数据的一致性和正确性。

对刚才代码稍作修改

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int num = 10000;void *routine(void *args)
{while (true){pthread_mutex_lock(&mutex);if (num > 0){usleep(100);printf("%d\n", num);num--;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}usleep(1000);}
}int main()
{const int n = 100;pthread_t t[n];for (size_t i = 0; i < n; i++){pthread_create(t + i, nullptr, routine, nullptr);}for (size_t i = 0; i < n; i++){pthread_join(t[i], nullptr);}pthread_mutex_destroy(&mutex);return 0;
}

这次运行结果就正常了

有了锁就能保证每次只有一个线程能够访问到临界资源

死锁

死锁是指两个或多个线程互相等待对方持有的资源而无法继续执行的情况

死锁的4个必要条件

  • 互斥:一个资源每次只能被一个执行流使用
  • 请求与保持:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待:若干执行流之间形成一种头尾相接的循环等待资源的关系

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致

线程同步

在多个线程互斥的访问临界资源时,只有一个能获得临界资源,其他线程只能忙等待,什么也做不了

两个问题:

  • 存在多个线程竞争同一个临界资源的情况,每次都是其中一个线程获得临界资源,造成其他线程的饥饿问题
  • 在临界资源没有就绪的时候,线程不停的申请锁—判断临界资源事否就绪—释放锁,一直重复,不合理

如何解决?条件变量
条件变量允许一个或多个线程等待满足某些条件时继续执行。

void push(const T &val)
{pthread_mutex_lock(&_mtx);while (is_full()){pthread_cond_wait(&_full, &_mtx);}_q.push(val);cout << "Producer Thread [ " << pthread_self() << " ]  Produced " << val << endl;pthread_mutex_unlock(&_mtx);pthread_cond_signal(&_empty);
}

截取一段代码举例

要点

  • 等待时线程自动挂起,并且释放手中的锁,如果收到唤醒信号,再在被阻塞的位置唤醒,重新去申请锁
  • 为什么使用while循环,不用if。要避免伪唤醒,if出现伪唤醒的情况时会直接向下运行,而while循环会再次检查资源事否就绪,防止伪唤醒

生产者消费者模型

请添加图片描述

核心点

  • 生产者在阻塞队列已满时不能继续放入数据
  • 消费者在阻塞队列为空时不能继续取出数据
  • 生产者与生产者之间互斥
  • 消费者与消费者之间互斥

阻塞队列

为了代码的健壮性,使用泛型编程

字段

阻塞队列是个队列,所以封装STL的队列

阻塞队列属于临界区,对临界区访问应该似乎互斥的,所以需要一把锁来控制生产者和消费者的互斥访问

条件变量,不让临界区外的资源忙等待

队列容量

函数

构造、析构

基本的入队列和出队列

判断是否空,是否满

#pragma once#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>using namespace std;
#define BLOCK_SIZE 10template <class T>
class BlockQueue
{
public:BlockQueue(size_t capacity = BLOCK_SIZE): _capacity(capacity){pthread_mutex_init(&_mtx, nullptr);pthread_cond_init(&_full, nullptr);pthread_cond_init(&_empty, nullptr);}void push(const T &val){pthread_mutex_lock(&_mtx);while (is_full()){pthread_cond_wait(&_full, &_mtx);}_q.push(val);cout << "Producer Thread [ " << pthread_self() << " ]  Produced " << val << endl;pthread_mutex_unlock(&_mtx);pthread_cond_signal(&_empty);}void pop(){pthread_mutex_lock(&_mtx);while (isEmpty()){pthread_cond_wait(&_empty, &_mtx);}T s = _q.front();_q.pop();cout << "Consumer Thread [ " << pthread_self() << " ]  Consumed " << s << endl;pthread_mutex_unlock(&_mtx);pthread_cond_signal(&_full);}~BlockQueue(){pthread_cond_destroy(&_full);pthread_cond_destroy(&_empty);pthread_mutex_destroy(&_mtx);}private:bool isEmpty(){return _q.empty();}bool is_full(){return _capacity == _q.size();}private:queue<T> _q;size_t _capacity;pthread_mutex_t _mtx;pthread_cond_t _full;pthread_cond_t _empty;
};

POSIX信号量

信号量的两个操作

  • p操作:申请资源
  • v操作:释放资源

基于环形队列的生产消费模型

允许生产者和消费者在同一个数据结构上进行操作

请添加图片描述

核心点

  • 生产者不能套圈消费者
  • 消费者不能超过生产者
  • 生产者与生产者之间互斥
  • 消费者与消费者之间互斥

环形队列

字段

封装vector

容量

生产者在队列的位置

消费者在队列的位置

消费信号

生产信号

消费锁

生产锁

函数

构造(这里没有实现析构,因为封装的锁和信号量各自的析构已经实现,析构时会自动调用)

入队列和出队列

#include <iostream>
#include <vector>
#include "Sem.hpp"
#include "Mutex.hpp"
#define SIZE 10template <class T>
class RingQueue
{
public:RingQueue(size_t size = SIZE): _size(size),_rq(size),_p_sem(size),_c_sem(0),_c_pos(0),_p_pos(0){}void push(const T &val){_p_sem.p();_p_mtx.lock();_rq[_p_pos++] = val;_p_pos %= _size;_p_mtx.unlock();_c_sem.v();}void pop(T *pv){_c_sem.p();_c_mtx.lock();*pv = _rq[_c_pos++];_c_pos %= _size;_c_mtx.unlock();_p_sem.v();}private:std::vector<T> _rq;size_t _size;size_t _c_pos;size_t _p_pos;Sem _c_sem;Sem _p_sem;Mutex _c_mtx;Mutex _p_mtx;
};

要点

  • 为什么是先申请资源(p操作),再加锁?申请信号量实际上一种“预定”,先买票后入座,这样可以确保每个进入临界区的线程要访问的资源已经就绪,可以提升效率。

对锁进行封装

#pragma once
#include <pthread.h>
class Mutex
{public:Mutex(){pthread_mutex_init(&_mtx, nullptr);}~Mutex(){pthread_mutex_destroy(&_mtx);}void lock(){pthread_mutex_lock(&_mtx);}void unlock(){pthread_mutex_unlock(&_mtx);}private:pthread_mutex_t _mtx;
};

对信号量进行封装

#pragma once#include <semaphore.h>
class Sem
{
public:Sem(int value){sem_init(&_sem, 0, value);}~Sem(){sem_destroy(&_sem);}void p(){sem_wait(&_sem);}void v(){sem_post(&_sem);}private:sem_t _sem;
};

线程池

详情代码见:实现简易线程池

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

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

相关文章

苹果电脑内存满了怎么清理空间垃圾 苹果电脑内存不足怎么办 MacBook优化储存空间

在日常使用苹果电脑过程中&#xff0c;某些用户可能经常会遇到存储空间不足的问题&#xff0c;尤其是当硬盘存储了大量的文件。这不仅影响电脑的运行速度&#xff0c;还可能导致应用程序运行不稳定。 一、节省 MacBook Pro 的空间 苹果电脑的操作系统&#xff08;macOS&#x…

大模型学习笔记3【大模型】LLaMA学习笔记

文章目录 学习内容LLaMALLaMA模型结构LLaMA下载和使用好用的开源项目[Chinese-Alpaca](https://github.com/ymcui/Chinese-LLaMA-Alpaca)Chinese-Alpaca使用量化评估 学习内容 完整学习LLaMA LLaMA 2023年2月&#xff0c;由FaceBook公开了LLaMA&#xff0c;包含7B&#xff0…

好用的便签怎么把重要的事项单独窗口显示?

在日常的工作和生活中&#xff0c;便签就像是我身边的小助手&#xff0c;随时记录着琐碎的事项&#xff0c;提醒我别忘了重要的任务。想象一下&#xff0c;早晨一到办公室&#xff0c;打开电脑&#xff0c;桌面上密密麻麻的便签就像一张张待办事项的清单&#xff0c;它们或提醒…

JAVA 快递100wms工具类

快递wms工具类 import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.google.gson.Gson; import com.kuaidi100.sdk.api.QueryTrack; import com.kuaidi100.sdk.api.Subscribe; import com.kuaidi100.sdk.contant.ApiInfoConstant; import c…

go开源webssh终端源码main.go分析

1.地址: https://github.com/Jrohy/webssh.git 2.添加中文注释地址: https://github.com/tonyimax/webssh_cn.git main.go分析 主包名&#xff1a;main package main //主包名 依赖包加载 //导入依赖包 import ("embed" //可执行文件…

解决js对象解构赋值多行格式被prettier格式化为一行的问题

目前没有特别好的解决方法&#xff0c;但是有一个hack方法&#xff0c;就是在第一个解构参数后面加个空注释&#xff0c;骗过prettier。 代码示例如下&#xff1a; const {prop1, //prop2,prop3, } props 欢迎关注公众号&#xff1a;清晰编程&#xff0c;获取更多精彩内容

方向导数和梯度

方向导数和梯度 1 导数的回忆2 偏导数及其向量形式偏导数的几何意义偏导数的向量形式 3 方向导数向量形式几何意义方向导数和偏导的关系 4 梯度5 梯度下降算法 1 导数的回忆 导数的几何意义如图所示&#xff1a; 当 P 0 P_{0} P0​点不断接近 P P P时&#xff0c;导数如下定义…

springboot私人诊所管理系统-计算机毕业设计源码93887

摘要 随着科技的不断发展和医疗服务的日益普及&#xff0c;私人诊所管理系统成为现代医疗管理的重要组成部分。该系统通过引入计算机技术和互联网平台&#xff0c;为患者提供方便快捷的就诊方式&#xff0c;同时也为诊所、医院提供高效的资源管理和服务优化的途径。本文将介绍私…

RocketMQ 顺序消息

顺序消息 顺序消息为云消息队列 RocketMQ 版中的高级特性消息&#xff0c;本文为您介绍顺序消息的应用场景、功能原理、使用限制、使用方法和使用建议。 应用场景 在有序事件处理、撮合交易、数据实时增量同步等场景下&#xff0c;异构系统间需要维持强一致的状态同步&#…

万字详解AI开发中的数据预处理(清洗)

数据清洗&#xff08;Data Cleaning&#xff09;是通过修改、添加或删除数据的方式为数据分析做准备的过程&#xff0c;这个过程通常也被称为数据预处理&#xff08;Data Preprocessing&#xff09;。对于数据科学家和机器学习工程师来说&#xff0c;熟练掌握数据清洗全流程至关…

21_硬件电路基础

目录 组合逻辑电路 组合逻辑电路原理 真值表 布尔代数 门电路 译码器 发光二极管LED 液晶字符显示器LCD 数据选择器 数据分配器 多路开关 时序逻辑电路 时序逻辑电路原理 时钟信号 触发器 电位触发方式触发器 边沿触发方式触发器 寄存器 移位器 计数器 总线…

经营人心:Mrs. B的百年传奇

这个故事的主角是Rose Blumkin&#xff0c;也被称为Mrs. B&#xff0c;她是Nebraska Furniture Mart的创始人。 她的故事确实是一个关于用户思维、客户关系和创业精神的经典案例。 Rose Blumkin于1893年出生在俄罗斯的一个小村庄&#xff0c;她在1921年移民到美国。 1937年&…

ImportError cannot import name ‘uic‘ from ‘PyQt5‘

ImportError cannot import name ‘uic’ from ‘PyQt5’ 1、描述 使用nuitka把PyQt5打包exe文件时报错: ImportError cannot import name ‘uic’ from ‘PyQt5’ 2、原因 这个是由于无法找到uic的目录导致的,在PyQt5的目录下是有uic文件的。 3、解决方案 找到导入uic…

SQL Server 2022的组成

《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;》图书介绍-CSDN博客 SQL Server 2022主要由4部分组成&#xff0c;分别是数据库引擎、分析服务、集成服务和报表服务。本节将详细介绍这些内容。 1.2.1 SQL Server 2022的数据库引擎 SQL Server 2022的…

NGINX+KEEPALIVED | 一文搞懂NG+KL负载均衡高可用架构的实操教程(详细)

文章目录 NGINXKEEPALIVED负载均衡高可用架构为什么需要多节点应用为什么需要Nginx服务为什么需要Keepalived服务NGKL简述前期准备Linux服务器公共环境配置Server1 NGKL服务器配置Server2 NGKL服务器配置Server3 HTTP服务器配置Server4 HTTP服务器配置运行测试用例 NGINXKEEPAL…

使用Keil将STM32部分程序放在RAM中运行

手动分配RAM区域,新建.sct文件,定义RAM_CODE区域,并指定其正确的起始地址和大小。 ; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************…

C语言 -- 函数

C语言 -- 函数 1. 函数的概念2. 库函数2.1 标准库和头文件2.2 库函数的使用方法2.2.1 功能2.2.2 头文件包含2.2.3 实践2.2.4 库函数文档的一般格式 3. 自定义函数3.1 函数的语法形式3.2 函数的举例 4. 形参和实参4.1 实参4.2 形参4.3 实参和形参的关系 5. return 语句6. 数组做…

Element中的消息提示组件Message和弹框组件MessageBox

简述&#xff1a;在 Element UI 中&#xff0c;Message和MessageBox都是比较常用的组件&#xff0c;Message用来提示消息&#xff0c;而MessageBox是一个用于创建模态对话框的组件。它可以用于在页面上快速展示信息、警告或错误提示&#xff0c;而不会阻止用户的其他操作。简单…

116-基于5VLX110T FPGA FMC接口功能验证6U CPCI平台

一、板卡概述 本板卡是Xilinx公司芯片V5系列芯片设计信号处理板卡。由一片Xilinx公司的XC5VLX110T-1FF1136 / XC5VSX95T-1FF1136 / XC5VFX70T-1FF1136芯片组成。FPGA接1片DDR2内存条 2GB&#xff0c;32MB Nor flash存储器&#xff0c;用于存储程序。外扩 SATA、PCI、PCI expres…

【期末复习】数据库系统概论(附带考点汇总)

第1章.绪论 目录 第1章.绪论1.1. 数据库系统概述1.1.1.基本概念1.1.2.产生和发展 1.2.概念模型1.2.1.三种模型1.2.2.概念模型1.2.3.关系模型 1.3.数据库系统结构1.3.1三级模式结构1.3.2.两级映像与数据独立性 第2章.关系型数据库2.1.关系2.2.关系操作2.2.1.基本关系操作2.2.2.关…