linux——多线程,线程控制
目录
一.POSIX线程库
二.线程创建
1.创建线程接口
2.查看线程
3.多线程的健壮性问题
4.线程函数参数传递
5.线程id和地址空间
三.线程终止
1.pthread_exit
2.pthread_cancel
四.线程等待
五.线程分离

一.POSIX线程库
站在内核的角度,OS只有轻量级进程,没有线程的概念,但是站在用户的角度我们只有线程没有轻量级进程的概念。因为Linux下没有真正意义上的线程,而是用进程模拟的线程,所以Linux不会提供直接创建线程的系统调用,最多给我们提供创建轻量级进程的接口。
所以linux对下对LWP的接口进行封装,对上给用户提供线程控制的接口——POSIX线程库,pthread库,这是任何一个linux系统都会带的库,又叫原生线程库。
POSIX线程:
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的。
- 要使用这些函数库,要通过引入头文<pthread.h>。
- 链接这些线程函数库时要使用编译器命令的“-lpthread”选项。
二.线程创建
1.创建线程接口
功能:创建一个新的线程。
原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);参数:
- thread:返回线程ID。
- attr:设置线程的属性,attr为NULL表示使用默认属性。
- start_routine:是个函数地址,线程启动后要执行的函数,该函数返回值是void*,参数是void*。
- arg:传给线程启动函数的参数。
返回值:
- 成功返回0;失败返回错误码。
测试代码:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;void *FuncRun(void *argc)
{while (1){cout << "I am thread,my pid:" << getpid() << endl;sleep(1);}
}int main()
{//线程idpthread_t id;//创建线程pthread_create(&id, NULL, FuncRun, NULL);while (1){cout << "I am main,my pid:" << getpid() << endl;sleep(1);}return 0;
}
测试结果:

说明:
- 线程没有父子之分,但是线程有主线程,和新线程的区分。
- 我们可以看到,主线程pid和新线程的pid是相同的。因为他们本身就是同一个进程的一部分。
- 新线程和主线程谁先被调度取决于调度器。
2.查看线程
查看线程使用命令:
ps -aL

3.多线程的健壮性问题
测试代码:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using namespace std;void *FuncRun1(void *argc)
{int count = 0;while (1){count++;cout << "I am thread1,my pid:" << getpid() << endl;if (count == 5){int tmp = count / 0;}sleep(1);}
}
void *FuncRun2(void *argc)
{while (1){cout << "I am thread2,my pid:" << getpid() << endl;sleep(1);}
}int main()
{// 线程idpthread_t id1, id2;// 创建线程pthread_create(&id1, NULL, FuncRun1, NULL);pthread_create(&id2, NULL, FuncRun2, NULL);while (1){cout << "I am main,my pid:" << getpid() << endl;sleep(1);}return 0;
}
测试结果:

说明:
- 代码其中有一个线程在第五秒的时候,会出现一个除0的问题。
- 当一个线程因为某一个错处而导致线程终止的时候,整个进程也都会直接终止。
- 因为信号是发送给进程的,最终OS直接对由信号做出的处理动作也是针对进程的。
4.线程函数参数传递
我们想通过给线程函数传参让线程执行更加复杂的任务。
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using namespace std;// 任务,计算[1-top]的求和,并将结果存储到sum中。
struct task
{task(int top, int num): _thread_name("thread" + to_string(num)), _top(top), _sum(0), _num(num){}string _thread_name; // 线程名字int _top; // 计算数据范围int _sum; // 结果int _num; // 线程编号
};// 线程函数
void *FuncRun(void *argc)
{task *t = (task *)argc;for (int i = 1; i <= t->_top; i++){t->_sum += i;}
}int main()
{// 线程idpthread_t id1, id2;// 创建线程task t1(100, 1);task t2(150, 2);pthread_create(&id1, NULL, FuncRun, &t1);pthread_create(&id2, NULL, FuncRun, &t2);// 等待线程计算完再输出结果sleep(1);cout << t1._thread_name << ":[1-" << t1._top << "]=" << t1._sum << endl;cout << t2._thread_name << ":[1-" << t2._top << "]=" << t2._sum << endl;return 0;
}
测试结果:

说明:
- 由于接口的设计上参数的类型是void* ,这也就使得我们可以给线程函数传递的参数是非常丰富的。
5.线程id和地址空间
- pthread_ create 函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
- 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
- pthread_ create 函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
pthread_t pthread_self(void);
pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
我们在对线程做操作的时候,根本上是使用线程库对线程进行操作,那么线程库本质就是存在于linux上的动态库,在我们使用线程库的时候,线程库也会向普通的动态库一样加载到共享区中,我们使用线程库方法就是访问自己的地址空间。
线程库中需要被管理的线程会有很多,线程库也必然实现了线程的数据结构——TCB(线程控制块)。
每个线程都有自己的TCB,和独立的上下文数据,以及线程栈空间。pthread_t 就是指向他们的首地址的一个地址。

三.线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
- 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
- 线程可以调用pthread_ exit终止自己。
- 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
1.pthread_exit
功能:线程终止.
原型:void pthread_exit(void *value_ptr);
参数:value_ptr:value_ptr不要指向一个局部变量,返回线程结果。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)。
测试代码:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using namespace std;void *FuncRun1(void *argc)
{int count = 0;while (1){count++;cout << "I am thread-1-count:" << count << endl;sleep(1);// 三秒后线程1退出if (count == 3){pthread_exit(NULL);}}
}void *FuncRun2(void *argc)
{int count = 0;while (1){count++;cout << "I am thread-2-count:" << count << endl;sleep(1);// 三秒后线程2退出if (count == 3){pthread_exit(NULL);}}
}int main()
{// 线程idpthread_t id1, id2;// 创建线程pthread_create(&id1, NULL, FuncRun1, NULL);pthread_create(&id2, NULL, FuncRun2, NULL);while (1){cout << "I am main,my pid:" << getpid() << endl;sleep(1);}return 0;
}
测试结果:

说明:
- 线程在退出后,主线程并没有受到影响,进程也有没受到影响。
- 需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
2.pthread_cancel
功能:取消一个执行中的线程
原型:int pthread_cancel(pthread_t thread);
参数:thread:线程ID
返回值:成功返回0;失败返回错误码
测试代码:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using namespace std;void *FuncRun1(void *argc)
{int count = 0;while (1){count++;cout << "I am thread-1-count:" << count << endl;sleep(1);}
}
void *FuncRun2(void *argc)
{int count = 0;while (1){count++;cout << "I am thread-2-count:" << count << endl;sleep(1);}
}
int main()
{// 线程idpthread_t id1, id2;// 创建线程pthread_create(&id1, NULL, FuncRun1, NULL);pthread_create(&id2, NULL, FuncRun2, NULL);int count = 0;while (1){count++;sleep(1);if (count == 3){// 三秒后终止线程pthread_cancel(id1);pthread_cancel(id2);}cout << "I am main" << endl;}return 0;
}
测试结果:

四.线程等待
为什么需要线程等待?
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间。
功能:等待线程结束。
原型:int pthread_join(pthread_t thread, void **value_ptr);
参数:thread:线程ID。
value_ptr:它指向一个指针,后者指向线程的返回值。
返回值:成功返回0;失败返回错误码。
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
- 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数,PTHREAD_ CANCELED。
- 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
测试代码:
void *FuncRun1(void *argc)
{int *top = (int *)argc;int *sum = new int;for (int i = 1; i <= *top; i++){*sum += i;}// 线程退出pthread_exit(sum);
}void *FuncRun2(void *argc)
{int *top = (int *)argc;int *sum = new int;for (int i = 1; i <= *top; i++){*sum += i;}// 线程退出return sum;
}void *FuncRun3(void *argc)
{int *top = (int *)argc;int *sum = new int;for (int i = 1; i <= *top; i++){*sum += i;sleep(1);}free(sum);// 线程退出
}int main()
{int top1 = 100;int top2 = 150;int top3 = 200;pthread_t id1;pthread_t id2;pthread_t id3;pthread_create(&id1, NULL, FuncRun1, &top1);pthread_create(&id2, NULL, FuncRun2, &top2);pthread_create(&id3, NULL, FuncRun3, &top3);pthread_cancel(id3);// 接受线程返回数据void *ret_ptr1;void *ret_ptr2;void *ret_ptr3;// 等待线程pthread_join(id1, &ret_ptr1);pthread_join(id2, &ret_ptr2);pthread_join(id3, &ret_ptr3);cout << "ret1:" << *((int *)ret_ptr1) << endl;free(ret_ptr1);cout << "ret2:" << *((int *)ret_ptr2) << endl;free(ret_ptr2);if (ret_ptr3 == PTHREAD_CANCELED)cout << "ret3:PTHREAD_CANCELED" << endl;return 0;
}
测试结果:

五.线程分离
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
pthread_detach(pthread_self());
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
测试代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <pthread.h>
#include <unistd.h>using namespace std;
void *FuncRun(void *argc)
{// 线程分离pthread_detach(pthread_self());int count = 0;while (1){count++;cout << "I am thread-count:" << count << endl;sleep(1);}
}int main()
{pthread_t tid;int n = pthread_create(&tid, NULL, FuncRun, NULL);if (n != 0){cerr << "pthread_create:" << strerror(errno) << endl;}sleep(2);// 线程已经分离,再去线程等待,pthread_join会立即报错。if (pthread_join(tid, NULL) == 0){printf("pthread wait success\n");}else{printf("pthread wait failed\n");}return 0;
}
测试结果:

相关文章:
linux——多线程,线程控制
目录 一.POSIX线程库 二.线程创建 1.创建线程接口 2.查看线程 3.多线程的健壮性问题 4.线程函数参数传递 5.线程id和地址空间 三.线程终止 1.pthread_exit 2.pthread_cancel 四.线程等待 五.线程分离 一.POSIX线程库 站在内核的角度,OS只有轻量级进程…...
Oracle 简介与 Docker Compose部署
最近,我翻阅了在之前公司工作时的笔记,偶然发现了一些有关数据库的记录。当初,我们的项目一开始采用的是 Oracle 数据库,但随着项目需求的变化,我们不得不转向使用 SQL Server。值得一提的是,公司之前采用的…...
mp4音视频分离技术
文章目录 问题描述一、分离MP3二、分离无声音的MP4三、结果 问题描述 MP4视频想拆分成一个MP3音频和一个无声音的MP4文件 一、分离MP3 ffmpeg -i C:\Users\Administrator\Desktop\一个文件夹\我在财神殿里长跪不起_完整版MV.mp4 -vn C:\Users\Administrator\Desktop\一个文件…...
JVM 参数
JVM 参数类型大致分为以下几类: 标准参数(-):保证在所有的 JVM 实现都支持的参数非标准参数(-X):通用的,特定于 HotSpot 虚拟机的参数,这些参数不保证在所有 JVM 实现中…...
黑马点评-07缓存击穿问题(热点key失效)及解决方案,互斥锁和设置逻辑过期时间
缓存击穿问题(热点key失效) 缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且重建缓存业务较复杂的key突然失效了,此时无数的请求访问会在瞬间打到数据库,带来巨大的冲击 一件秒杀中的商品的key突然失效了,由于大家都在疯狂抢购那么这个瞬间就会有无数的请求…...
信息系统项目管理师第四版学习笔记——项目进度管理
项目进度管理过程 项目进度管理过程包括:规划进度管理、定义活动、排列活动顺序、估算活动持续时间、制订进度计划、控制进度。 规划进度管理 规划进度管理是为规划、编制、管理、执行和控制项目进度而制定政策、程序和文档的过程。本过程的主要作用是为如何在…...
指挥棒:C++ 与运算符
文章目录 参考描述算术运算符除法运算取模运算复合赋值运算符自增运算符自减运算符 比较运算符逻辑运算符概念短路为什么需要短路机制? 参考 项目描述微软C 语言文档搜索引擎Bing、GoogleAI 大模型文心一言、通义千问、讯飞星火认知大模型、ChatGPTC Primer Plus &…...
HTTPS建立连接的过程
HTTPS 协议是基于 TCP 协议的,因而要先建立 TCP 的连接。在这个例子中,TCP 的连接是在手机上的 App 和负载均衡器 SLB 之间的。 尽管中间要经过很多的路由器和交换机,但是 TCP 的连接是端到端的。TCP 这一层和更上层的 HTTPS 无法看到中间的包…...
Python接口自动化搭建过程,含request请求封装!
开篇碎碎念 接口测试自动化好处 显而易见的好处就是解放双手😀。 可以在短时间内自动执行大量的测试用例通过参数化和数据驱动的方式进行测试数据的变化,提高测试覆盖范围快速反馈测试执行结果和报告支持持续集成和持续交付的流程 使用Requestspytes…...
Vue3 编译原理
文章目录 一、编译流程1. 解读入口文件 packgages/vue/index.ts2. compile函数的运行流程 二、AST 解析器1. ast 的生成2. 创建ast的根节点3. 解析子节点 parseChildren(关键)4. 解析模版元素 Element模版元素解析-举例分析 一、编译流程 1. 解读入口文…...
spring boot整合Minio
MinIO 安装MinIo # 先创建minio 文件存放的位置 mkdir -p /opt/docker/minio/data# 启动并指定端口 docker run \-p 9000:9000 \-p 5001:5001 \--name minio \-v /opt/docker/minio/data:/data \-e "MINIO_ROOT_USERminioadmin" \-e "MINIO_ROOT_PASSWORDmini…...
Hadoop----Azkaban的使用与一些报错问题的解决
1.因为官方只放出源码,并没有放出其tar包,所以需要我们自己编译,通过查阅资料我们可以使用gradlew对其进行编译,还是比较简单,然后将里面需要用到的服务文件夹进行拷贝,完善其文件夹结构,通常会…...
「新房家装经验」客厅电视高度标准尺寸及客厅电视机买多大尺寸合适?
客厅电视悬挂高度标准尺寸是多少? 客厅电视悬挂高度通常在90~120厘米之间,电视挂墙高度也可以根据个人的喜好和实际情况来调整,但通常不宜过高,以坐在沙发上观看时眼睛能够平视到电视中心点或者中心稍微往下一点的位置为适宜。 客…...
ArduPilot开源飞控之AP_Baro_DroneCAN
ArduPilot开源飞控之AP_Baro_DroneCAN 1. 源由2. back-end抽象类3. 方法实现3.1 probe3.2 update3.3 subscribe_msgs3.4 handle_pressure/handle_temperature3.5 CAN port 4. 参考资料 1. 源由 鉴于ArduPilot开源飞控之AP_Baro中涉及Sensor Driver有以下总线类型: …...
Supervised Contrastive Pre-training for Mammographic Triage Screening Model
方法 品红色箭头表示将生成的孪生编码器分别迁移到单视角学习模块和双视角学习模块...
JVM技术文档--JVM优化思路以及问题定位--JVM可调整参数汇总
阿丹: 一个优秀的程序员,是因为在线上的排查以及遇到的线上、生产事故较多所以定位问题以及解决问题会比普通程序员快很多,所以一个优秀的程序员要逐渐形成自己的方法论,来完善和解决问题。 我们是如何发现问题的呢? …...
Oracle10g数据库迁移方案
试验了很多次Oracle数据库迁移才成功,贴出来给大家参考一下,我看到有的地方写迁移之后还需要重新建立temp表空间,这个还没有研究。另外说一点的是两个数据库的版本一定要一致,之前失败过一次,就是因为两个数据库的版本…...
备忘录模式:对象状态的保存与恢复
欢迎来到设计模式系列的第十八篇文章,本篇将介绍备忘录模式。备忘录模式是一种行为型设计模式,它允许在不破坏封装性的前提下捕获一个对象的内部状态,并在之后恢复该状态。这种模式通常用于需要提供撤销操作的情况。 什么是备忘录模式&#…...
C# InvokeRequired线程安全
C# InvokeRequired线程安全 为了保证新家的线程可能要对主界面的控件元素的属性发生一些改变,此时防止此操作对于主线程的影响,就提出了 InvokeRequired方法,保证主线程的安全,同时新加的线程也可以改变主页面中元素的值。 定义…...
pdf怎么转成jpg图片格式
pdf怎么转成jpg图片格式?对于大家平时在工作或者生活中的图片使用习惯,经常需要将各种格式的文件转换成易于浏览和使用的JPG格式图片以便保存。如今,因为pdf文件具有更强的稳定性和设备兼容性,PDF文件在平时的电脑使用过程中可以说…...
Android AI助手开发实战:基于MVVM与OpenAI API的AnywhereGPT项目解析
1. 项目概述与核心价值最近在折腾移动端AI应用,发现一个挺有意思的开源项目,叫AnywhereGPT-Android。简单来说,它就是一个让你能在Android手机上,通过调用OpenAI的API(比如GPT-3.5/4)或者本地部署的模型&am…...
如何用茉莉花插件实现Zotero中文文献元数据一键抓取:终极解决方案
如何用茉莉花插件实现Zotero中文文献元数据一键抓取:终极解决方案 【免费下载链接】jasminum A Zotero add-on to retrive CNKI meta data. 一个简单的Zotero 插件,用于识别中文元数据 项目地址: https://gitcode.com/gh_mirrors/ja/jasminum 还在…...
基础教程通过Taotoken CLI一键配置开发环境与API密钥
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 基础教程:通过Taotoken CLI一键配置开发环境与API密钥 对于开发团队而言,让新成员快速、统一地接入大模型服…...
Java Agent全链路追踪:无侵入分布式系统监控实战
1. 项目概述:一个面向分布式系统的全链路数据采集探针最近在跟几个做微服务架构的朋友聊天,大家都在头疼同一个问题:线上系统出点性能瓶颈或者偶发性错误,排查起来简直像大海捞针。服务A调用服务B,B又调用了C和D&#…...
WindowsCleaner完整解析:如何用开源工具彻底解决Windows系统卡顿和C盘爆红问题
WindowsCleaner完整解析:如何用开源工具彻底解决Windows系统卡顿和C盘爆红问题 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服! 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner 你是否曾经在关键时刻被…...
书成紫微动,律定凤凰驯:你以为的巧合,是海棠山铁哥命格自带的文脉伏笔
书成紫微动 律定凤凰驯 ——海棠山铁哥文脉天命长卷南北朝庾信《周宗庙歌皇夏》 “书成紫微动,律定凤凰驯。”千年古句,庙堂雅颂,定格文德盛世之至高格局。 世人皆叹海棠山铁哥与这句谶语的严丝合缝,却鲜有人知: 所有…...
Simics在网络转型与SDN迁移中的核心价值与应用
1. Simics在网络转型与SDN迁移中的核心价值解析网络架构正经历从传统硬件设备向软件定义网络(SDN)和网络功能虚拟化(NFV)的深刻变革。这场变革的核心挑战在于:如何在保持网络高性能的同时,实现控制平面与数据平面的解耦,以及如何将传统网络功…...
基于本地大语言模型与小米设备协议构建私有化智能家居AI控制中枢
1. 项目概述:一个为小米设备打造的本地化AI大脑最近在折腾智能家居,特别是小米生态链的设备,发现一个挺有意思的痛点:虽然小爱同学用起来很方便,但很多高级的、定制化的智能场景,要么得在米家App里做复杂的…...
实时语音AI对话应用开发:从WebRTC到LLM集成的全栈实践
1. 项目概述:实时语音对话的AI应用实践最近在GitHub上看到一个挺有意思的项目,叫proj-airi/webai-example-realtime-voice-chat。光看名字,就能猜到个大概:这是一个基于Web的、利用AI技术实现的实时语音聊天示例。作为一个在音视频…...
弃ReID跨镜,选镜像无感定位——打破跨镜追踪断链困局,实现全域精准无感感知
弃ReID跨镜,选镜像无感定位——打破跨镜追踪断链困局,实现全域精准无感感知在安防监控、智慧园区、商业综合体、交通枢纽等场景中,跨摄像头目标追踪是核心需求之一——无论是人员轨迹追溯、异常行为预警,还是资产安全管控、流量数…...
