当前位置: 首页 > news >正文

多线程控制讲解与代码实现

多线程控制

回顾一下线程的概念

线程是CPU调度的基本单位,进程是承担分配系统资源的基本单位。linux在设计上并没有给线程专门设计数据结构,而是直接复用PCB的数据结构。每个新线程(task_struct{}中有个指针都指向虚拟内存mm_struct结构,实现了共享同一份代码,拥有该进程的一部分资源)

在linux中,把所有执行流都看作是轻量级进程,故有一个用户级的原生线程库为用户提供“线程”接口(对OS是轻量级线程)。

从信号、异常和资源看线程的健壮性问题

一个线程出现异常,会影响其他线程

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>using namespace std;void* start_routime(void* args)
{string name = static_cast<const char*>(args);//安全地进行强制类型转换// 如果是一个执行流,那么不可能同时执行2个死循环int count = 0;while(1){cout << "new thread is created! name: " << name << endl;sleep(1);count++;if(count == 5){int* p = nullptr;*p = 10;//故意写一个解引用空指针,我们知道是会报段错误}}
}int main()
{pthread_t thread;pthread_create(&thread, nullptr, start_routime, (void*)"thread new");while(1){cout << "new thread is created! name: main" << endl;sleep(1);}return 0;
}

命令行报错

[yyq@VM-8-13-centos 2023_03_18_multiThread]$ ./mythread 
new thread is created! name: main
new thread is created! name: thread new
new thread is created! name: main
new thread is created! name: thread new
new thread is created! name: main
new thread is created! name: thread new
new thread is created! name: main
new thread is created! name: thread new
new thread is created! name: main
new thread is created! name: thread new
new thread is created! name: main
Segmentation fault

由此可以看出,当一个线程出异常了,会直接影响其他线程的正常运行。因为信号是整体发送给进程的,本质是将信号发给对应进程的pid,而一个进程的所有线程pid值是相同的,就会给每个线程的PCB里写入相同的信号,接收到信号后,所有的进程就退了。

换个角度来说,每个线程所依赖的资源是进程给的,当一个线程出异常,进程会收到退出信号后,OS回收资源是回收整个进程的资源,而其他线程的资源是进程给的,故所有的线程就会全部退出。

以上是从信号+异常+资源的视角来看待线程健壮性的问题。

POSIX线程库的errno

我们学习的是POSIX线程库,有以下特征

1、与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是pthread_打头的;

2、要使用这些函数库,要通过引入头文<pthread.h>

3、链接这些线程函数库时要使用编译器命令的-l pthread选项。

用户级线程库的pthread这一类函数出错时不会设置全局变量errno(虽然大部分其他POSIX函数会这样做),而是将错误代码通过返回值返回。因为线程是共享一份资源的,如果多个线程对同一个全局变量进行访问(errno也是全局变量),就会因为缺乏访问控制而带来一些问题,因此对于pthreads函数的错误,建议通过返回值来判定。

简单了解clone

允许用户创建一个进程/轻量级进程,fork()/vfork()就是调用clone来实现的。

#include <sched.h>
功能:创建一个进程/轻量级进程原型int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, .../* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );参数child_stack:子栈(用户栈)flags:
返回值创建失败,返回-1;创建成功,返回线程ID/* Prototype for the raw system call */long clone(unsigned long flags, void *child_stack, void *ptid, void *ctid, struct pt_regs *regs);

创建多线程

#include <iostream>
#include <string>
#include <vector>
#include <pthread.h>
#include <unistd.h>using namespace std;void* start_routime(void* args)
{string name = static_cast<const char*>(args);//安全地进行强制类型转换while(1){cout << "new thread is created! name: " << name << endl;sleep(1);}
}int main()
{vector<pthread_t> threadIDs (10, pthread_t());for(size_t i = 0; i < 10; i++){pthread_t tid;char nameBuffer[64];snprintf(nameBuffer, sizeof(nameBuffer), "%s : %d", "thread", i);pthread_create(&tid, nullptr, start_routime, (void*)nameBuffer);//是缓冲区的起始地址,无法保证创建的新线程允许先后次序threadIDs[i] = tid;sleep(1);}for(auto e : threadIDs){cout << e << endl;}while(1){cout << "new thread is created! name: main" << endl;sleep(1);}return 0;
}

现象:当创建多个线程的循环中没有添加sleep语句时,我们可能看到的输出一直是某个线程。

分析:当我们创建新的线程时,每个线程是独立的执行流。首先,创建多个新线程,谁先运行是不确定的;其次,因为nameBuffer是被所有线程共享的,主线程是把缓冲区的起始地址传给每个线程,在循环里nameBuffer在一直被主进程更新,所以每个进程能拿到的都是被主进程更新后的最新的进程id。

多线程数据私有

所以,如果我们想让各个线程独立执行代码,这样的写法是不对的,那如何给线程传递正确的结构呢?既然nameBuffer是同一个变量一样的地址,那就每次传入不同的地址呀

//当成结构体使用
class ThreadData
{
public:pthread_t tid;char nameBuffer[64];    
};
//对应的操作函数如下
void* start_routime(void* args)//args传递的时候,也是拷贝了一份地址,传过去。不管是传值传参还是传引用传参,都会发生拷贝
{ThreadData* td = static_cast<ThreadData*>(args);//安全地进行强制类型转换int cnt = 10;while(cnt){cout << "new thread is created! name: " << td->nameBuffer << " 循环次数cnt:" << cnt << endl;cnt--;sleep(1);}delete td;return nullptr;
}
int main()
{vector<ThreadData*> threads;for(size_t i = 0; i < 10; i++){// 此处 td是指针,传给每个线程的td指针都是不一样的,实现数据私有ThreadData* td = new ThreadData();snprintf(td->nameBuffer, sizeof(td->nameBuffer), "%s : %d", "thread", i + 1);pthread_create(&td->tid, nullptr, start_routime, (void*)td);threads.push_back(td);}for(auto& e : threads){cout << "create thread name: " << e->nameBuffer << " tid:" << e->tid << endl;}int cnt = 7;while(cnt){cout << "new thread is created! name: main" << endl;cnt--;sleep(1);}return 0;
}

通过传new出来的结构体指针,实现多线程数据私有!

重入状态

start_routime()函数同时被10个线程访问,在程序运行期间处于重入状态。站在变量的角度,由于函数没有访问全局变量,访问的都是局部变量,故是可重入函数。【严格来说,这不算可重入函数,因为cout是访问文件的,而我们只有一个显示器,在输出到显示器的时候有可能会出错】

对全局变量进行原子操作的是可重入函数。

独立栈空间

每个线程都有自己独立的栈空间

线程ID

线程id是它独立栈空间的起始地址

线程等待pthread_join

**join是阻塞式等待。**线程也是要被等待的,如果不等待,会造成类似僵尸进程的问题–内存泄漏。作用1、获取线程退出信息;2、回收线程资源。但与进程不同的是,线程不用获取退出信号,因为一旦线程出异常,收到信号了,整个进程都会退出。

pthread_join不考虑异常问题,线程出异常了进程直接来处理。

start_routime返回值的类型是void*,pthread_join()中retval参数的类型是void**。两者之间有关联。

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数thread:线程idretval输出型参数:用于获取线程函数结束时,返回的退出结果
返回值成功返回0,失败返回错误码具体使用:
void* retval = nullptr;//相当于把start_routime返回的指针(这里的指针是指针地址,是个字面值)存到ret(这里的ret是指针变量)里面去
int n = pthread_join(tid, &retval);
assert(n == 0);

线程终止时,可以返回一个指针(比如堆空间的地址、对象的地址等),并可以被主线程取到,由此可以完成信息交互。

例如进程,有阻塞式等待和非阻塞式等待,用信号捕捉函数,设置成signal(SIGCHLD, SIG_IGN);就可;而进程没有非阻塞式等待。

线程分离pthread_detach

默认情况下,我们创建的新线程都是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏,如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

功能:分离线程,与joinable是互斥的
#include <pthread.h>
原型:int pthread_detach(pthread_t thread);
返回值:成功返回0;失败返回错误码,但不设置错误码,不被设置到errno
//使用1:线程自己分离自己
pthread_detach(pthread_self());
//使用2:主线程分离其他线程

当线程自己分离自己后,主线程再调用pthread_join()【需要主动让主进程的join后与detach执行】,此时jion函数会返回22,表示Invalid argument

为什么要先让detach执行,因为新线程和主线程谁先执行是不确定的,当新线程去执行自己的任务时,假设新线程还没来得及执行detach,而主线程就已经join了,那么detach就无效了。

功能:获取调用该函数的线程ID
#include <pthread.h>
pthread_t pthread_self(void);

线程终止

线程退出return/pthread_exit

  1. return nullptr; return返回就表示该线程终止。

  2. pthread_exit(nullptr); 线程退出的专用pthread_exit()函数。

    #include <pthread.h>
    void pthread_exit(void *retval);

exit用于终止进程,不能用于终止线程。任何一个执行流调用exit都会让整个进程退出。

发现了没,return和pthread_exit都有个nullptr参数!这个返回值会放在pthread库里面的。

后续线程等待时,就是到pthread库里取到这个值。

线程取消pthread_cancel

线程被取消的前提是线程已经在运行了,由主线程给对应的线程发送取消命令。收到的退出码retval是-1,-1实际上是宏#define PTHREAD_CANCELED ((void*) -1)

原生线程库pthread

站在上层的角度(从语言层面)来看原生线程库:

在linux上,任何语言如果要实现多线程,必定要用到pthread库,如何看待C++11中的多线程呢?C++11中的多线程在linux环境下本质是对pthread库的封装

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;void thread_run()
{int cnt = 5;while(cnt){cout << "我是新线程" << endl;sleep(1);}
}int main()
{thread t1(thread_run);while(true){cout << "我是主线程" << endl;}t1.join();return 0;
}
//这份代码用g++编译,如果不带-lpthread选项,就会报错!说明C++就是封装了原生线程库

用原生线程库写出来的代码,是不可跨平台的,但是效率更高;用C++写出来的多平台通用,但是效率偏低。

原生线程库是共享库,可以同时被多个用户使用。那么如何对用户创建出来的线程做管理呢?

linux给出的解决方案是,让原生线程库采用一定的方法对用户创建的线程做管理,只不过在库里需要添加的线程属性比较少(包括线程id等),会存在一个union pthread_attr_t{};结构体里,然后与内核中的轻量级进程一一对应,内核提供线程执行流的调度。linux用户级线程:内核轻量级进程=1:1

在这里插入图片描述

线程局部存储__thread

全局变量保存在进程地址空间的已初始化数据区的,被__thread修饰的全局变量保存在线程局部存储中(共享区的线程结构体里)。这个是线程独有的,介于全局变量和局部变量之间的一种存储方案。

相关文章:

多线程控制讲解与代码实现

多线程控制 回顾一下线程的概念 线程是CPU调度的基本单位&#xff0c;进程是承担分配系统资源的基本单位。linux在设计上并没有给线程专门设计数据结构&#xff0c;而是直接复用PCB的数据结构。每个新线程&#xff08;task_struct{}中有个指针都指向虚拟内存mm_struct结构&am…...

清晰概括:进程与线程间的区别的联系

相关阅读&#xff1a; &#x1f517;通俗简介&#xff1a;操作系统之进程的管理与调度&#x1f517;如何使用 jconsole 查看Java进程中线程的详细信息&#xff1f; 目录 一、进程与线程 1、进程 2、线程 二、进程与线程之间的区别和联系 1、区别 2、联系 一、进程与线程 …...

自定义类型 (结构体)

文章目录&#x1f4ec;结构体的声明&#x1f50e;1.结构的基础知识&#x1f50e;2.结构的声明&#x1f50e;3.特殊的声明&#x1f50e;4.结构的自引用&#x1f50e;5.结构体变量的定义和初始化&#x1f50e;6.结构体内存对齐&#x1f50e;7.修改默认对齐数&#x1f50e;8.结构体…...

第14届蓝桥杯STEMA测评真题剖析-2023年3月12日Scratch编程初中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第113讲。 蓝桥杯选拔赛现已更名为STEMA&#xff0c;即STEM 能力测试&#xff0c;是蓝桥杯大赛组委会与美国普林斯顿多…...

程序员接私活一定要知道的事情,我走的弯路你们都别走了

文章目录前言一、程序员私活的种类1.兼职职位众包2.自由职业者驻场3.项目整包二、这3种私活可以接1.有熟人2.七分熟的项目3.需求明确的项目三、这3种私活不要接1.主动找上门的中介单2.一味强调项目简单好做3.外行人给你拉的项目四、接单的渠道1.线下渠道2.线上渠道3.比较靠谱的…...

十二届蓝桥杯省赛c++(下)

1、 拿到题目一定要读懂题意&#xff0c;不要看到这题目就上来模拟什么闰年&#xff0c;一月的天数啥的。这个题目问你当天的时间&#xff0c;就说明年月日跟你都没关系&#xff0c;直接无视就好了。 #include <iostream> #include <cstring> #include <algori…...

数据结构与算法——堆的基本存储

目录 一、概念及其介绍 二、适用说明 三、结构图示 四、Java 实例代码 五.堆和栈的区别 一、概念及其介绍 堆(Heap)是计算机科学中一类特殊的数据结构的统称。 堆通常是一个可以被看做一棵完全二叉树的数组对象。 堆满足下列性质&#xff1a; 堆中某个节点的值总是不大…...

来了来了 !!!K8s指令、yaml部署

文章目录k8s资源清单一、k8s资源指令1、基础操作2、命令手册二、资源清单1、required2、optional3、other4、资源清单格式5、常用命令三、部署实例1、nginx3、eureka部署k8s资源清单 一、k8s资源指令 1、基础操作 #创建且运行一个pod #deployment、rs、pod被自动创建 kubect…...

spring-cloud-feign实战笔记

feign 配置 针对单个feign接口进行配置feign:client:config:# feignName 注意这里与contextId一致&#xff0c;不能写成name&#xff08;FeignClientFactoryBean#configureFeign&#xff09;# 不能写成 client-b (微服务名称)&#xff0c;否则不生效helloFeignClient: # conte…...

【Pytorch】利用PyTorch实现图像识别

本文参加新星计划人工智能(Pytorch)赛道&#xff1a;https://bbs.csdn.net/topics/613989052 这是目录使用torchvision库的datasets类加载常用的数据集或自定义数据集使用torchvision库进行数据增强和变换&#xff0c;自定义自己的图像分类数据集并使用torchvision库加载它们使…...

在家查找下载最新《柳叶刀》The Lancet期刊文献的方法

《柳叶刀》The Lancet简介&#xff1a; 《柳叶刀》The Lancet是全球顶尖综合性医学期刊&#xff0c;每周都会发表来自世界各地顶尖科学家的研究精粹。是由托马斯威克利&#xff08;Thomas Wakley&#xff09;创办于1823年&#xff0c;由爱思唯尔&#xff08;Elsevier&#xff…...

当下的网络安全行业前景到底怎么样?还能否入行?

前言网络安全现在是朝阳行业&#xff0c;缺口是很大。不过网络安全行业就是需要技术很多的人达不到企业要求才导致人才缺口大常听到很多人不知道学习网络安全能做什么&#xff0c;发展前景好吗&#xff1f;今天我就在这里给大家介绍一下。网络安全作为目前比较火的朝阳行业&…...

SpringCloud:SpringAMQP介绍

Spring AMQP是基于RabbitMQ封装的一套模板&#xff0c;并且还利用SpringBoot对其实现了自动装配&#xff0c;使用起来非常方便。Spring AMQP官方地址 Spring AMQP提供了三个功能&#xff1a; 自动声明队列、交换机及其绑定关系基于注解的监听器模式&#xff0c;异步接收消息封…...

第十三届蓝桥杯省赛 python B组复盘

文章目录前言主要内容&#x1f99e;试题 A&#xff1a;排列字母思路代码&#x1f99e;试题 B&#xff1a;寻找整数思路代码&#x1f99e;试题 C&#xff1a;纸张尺寸思路代码&#x1f99e;试题 D&#xff1a;数位排序思路代码&#x1f99e;试题 E&#xff1a;蜂巢思路代码&…...

SQL注入之HTTP请求头注入

Ps&#xff1a; 先做实验&#xff0c;在有操作的基础上理解原理会更清晰更深入。 一、实验 sqli-lab 1. User-Agent注入 特点&#xff1a;登陆后返回用户的 User-Agent --> 服务器端可能记录用户User-Agent 输入不合法数据报错 payload: and updatexml(1,concat("~&…...

Metasploit详细教程

第一步&#xff1a;安装和启动Metasploit 您可以从Metasploit官方网站下载适用于您操作系统的Metasploit框架。安装Metasploit框架后&#xff0c;您可以使用以下命令来启动Metasploit&#xff1a; msfconsole该命令将启动Metasploit控制台。 第二步&#xff1a;查找目标设备…...

【ChatGPT】Notion AI 从注册到体验:如何免费使用

欢迎关注【youcans的GPT学习笔记】原创作品&#xff0c;火热更新中 【ChatGPT】Notion AI 从注册到体验1. Notion AI 介绍1.1 Notion AI 简介1.2 Notion AI 的核心能力1.3 Notion AI 与 ChatGPT 的比较2. Notion AI 国内用户注册2.1 PC 端用户注册2.2 移动端用户注册3. Notion …...

每个开发人员都需要掌握的10 个基本 SQL 命令

SQL 是一种非常常见但功能强大的工具&#xff0c;它可以帮助从任何数据库中提取、转换和加载数据。数据查询的本质在于SQL。随着公司和组织发现自己处理的数据量迅速增加&#xff0c;开发人员越来越需要有效地使用数据库来处理这些数据。所以想要暗恋数据领域&#xff0c;SQL是…...

Vue项目预渲染

前言 Ajax 技术的出现&#xff0c;让我们的 Web 应用能够在不刷新的状态下显示不同页面的内容&#xff0c;这就是单页应用。在一个单页应用中&#xff0c;往往只有一个 html 文件&#xff0c;然后根据访问的 url 来匹配对应的路由脚本&#xff0c;动态地渲染页面内容。单页应用…...

可别再用BeanUtils了(性能拉胯),试试这款转换神器

老铁们是不是经常为写一些实体转换的原始代码感到头疼&#xff0c;尤其是实体字段特别多的时候。有的人会说&#xff0c;我直接使用get/set方法。没错&#xff0c;get/set方法的确可以解决&#xff0c;而且也是性能较高的处理方法&#xff0c;但是大家有没有想过&#xff0c;要…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

Python 训练营打卡 Day 47

注意力热力图可视化 在day 46代码的基础上&#xff0c;对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

《信号与系统》第 6 章 信号与系统的时域和频域特性

目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...

【题解-洛谷】P10480 可达性统计

题目&#xff1a;P10480 可达性统计 题目描述 给定一张 N N N 个点 M M M 条边的有向无环图&#xff0c;分别统计从每个点出发能够到达的点的数量。 输入格式 第一行两个整数 N , M N,M N,M&#xff0c;接下来 M M M 行每行两个整数 x , y x,y x,y&#xff0c;表示从 …...