Linux系统编程:线程控制

目录

一. 线程的创建

1.1 pthread_create函数

1.2 线程id的本质

二. 多线程中的异常和程序替换

2.1 多线程程序异常

2.2 多线程中的程序替换

三. 线程等待

四. 线程的终止和分离

4.1 线程函数return

4.2 线程取消 pthread_cancel

4.3 线程退出 pthread_exit

4.4 线程分离 pthread_detach 

五. 总结


一. 线程的创建

1.1 pthread_create函数

函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(start_routine)(void*), void *args)

函数功能:创建新线程

函数参数:

        thread -- 输出型参数,用于获取新线程的id

        attr -- 设置线程属性,一般采用nullptr,表示为默认属性

        start_routine -- 新创建线程的入口函数

        args -- 传入start_routine函数的参数

返回值:成功返回0,失败返回对应错误码

关于pthread系列函数的错误检查问题:

  • 一般的Linux系统调用相关函数,都是成功返回0,失败返回-1。
  • 但函数pthread系列函数不是,这些函数都是成功返回0,失败返回错误码,不对全局错误码进行设置。

代码1.1演示了如何通过pthread_create函数创建线程,在主函数中,分别以%lld和%x的方式输出子线程id,图1.1为代码的运行结果。

代码1.1:创建线程

#include <iostream>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>// 新建线程的入口函数
void *threadRoutine(void *args)
{while(true){std::cout << (char*)args << std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;   // 接收子线程id的输出型参数// 调用pthread_create函数创建线程// tid接收新线程的id,nullptr表示新线程为默认属性// 新线程的入口函数设为threadRoutine,参数为"thread 1"int n = pthread_create(&tid, nullptr, threadRoutine, (char*)"thread 1");if(n != 0)  // 检验新线程是否创建成功{std::cout << "error:" << strerror(n) << std::endl;exit(1);}while(true){printf("main thread, tid = %lld 0x%x\n", tid, tid);sleep(1);}return 0;
}
图1.1  代码1.1的运行结果

1.2 线程id的本质

如1.2所示,在Linux的线程库pthread中,提供了用于维护每个线程的属性字段,包括描述线程的结构体struct pthread、线程的局部存储、线程栈等,用于对每个线程的维护。

每个线程在线程库中用于维护它的属性字段的起始地址,就是这个线程的id,换言之,线程id就是动态库(地址空间共享区)的一个地址,Linux为64位环境,因此,代码1.1输出的线程id会很大,这个值就对应地址空间共享区的位置。

为了保证每个线程的栈区是独立的,Linux采用的方法是线程栈在用户层提供,这样每个线程都会在动态线程库内部分得一块属于自身的“栈区”,这样就可以保证线程栈的独立性,而主线程的栈区,就使用进程地址空间本身的栈区。

Linux保证线程栈区独立性的方法: 

  • 子线程的栈区在用户层提供。
  • 主线程栈区采用地址空间本身的栈区。

线程id的本质:地址空间共享区的一个地址。

图1.2  线程id的图解

二. 多线程中的异常和程序替换

2.1 多线程程序异常

在多线程程序中,如果某个线程在执行期间出现了异常,那么整个进程都可能会退出,在多线程场景下,任意一个线程出现异常,其影响范围都是整个进程

如代码2.1创建了2个子线程,其中threadRun2函数中人为创造除0错误引发异常,发现整个进程都退出了,不会出现只有一个线程终止的现象。

结论:任意一个线程出现异常,其影响范围都是整个进程,会造成整个进程的退出。

代码2.1:多线程程序异常

#include <iostream>
#include <pthread.h>
#include <unistd.h>void *threadRoutine1(void *args)
{while(true){std::cout << (char*)args << std::endl;sleep(1);}return nullptr;
}void *threadRoutine2(void *args)
{while(true){std::cout << "thread 2, 除0错误!" << std::endl;int a = 10;a /= 0; }return nullptr;
}int main()
{pthread_t tid1, tid2;//先后创建线程1和2pthread_create(&tid1, nullptr, threadRoutine1, (void*)"thread 1");sleep(1);pthread_create(&tid2, nullptr, threadRoutine2, (void*)"thread 2");while(true){std::cout << "main thread ... ... " << std::endl;sleep(1);}return 0;
}
图2.1  代码2.1的运行结果

2.2 多线程中的程序替换

与多线程中线程异常类似,多线程中某个线程如果进行了程序替换,那么并不会出现这个线程去运行新的程序,其他线程正常执行原来的工作的情况,而是整个进程都被替换去执行新的程序。

代码2.2在threadRoutine1函数中通过execl去执行系统指令ls,运行代码我们发现,在子线程中进行程序替换后,主线程也不再继续运行了,进程执行完ls指令,就终止了。

结论:多线程程序替换是整个进程都被替换,而不是只替换一个线程。

代码2.2:多线程程序替换

#include <iostream>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>void *threadRoutine1(void *args)
{while(true){std::cout << (char*)args << std::endl;execl("/bin/ls", "ls", nullptr);   // 子线程中进行程序替换exit(0);}return nullptr;
}int main()
{pthread_t tid;// 创建线程int n = pthread_create(&tid, nullptr, threadRoutine1, (void*)"thread 1");if(n != 0)  {// 检验线程创建成功与否std::cout << strerror(n) << std::endl;exit(1);}while(true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}
图2.2  代码2.2的运行结果

三. 线程等待

线程等待与进程等待类似,主线程需要等待子线程退出,以获取子线程的返回值。如果主线程不等待子线程,而主线程也不退出,那么子线程就会处于“僵尸状态”,其task_struct一直得不到释放,引起内存泄漏。

  • 通过pthread_join函数,可以实现对线程的等待。
  • 线程等待只能是阻塞等待,不能非阻塞等待

pthread_join函数 -- 等待线程

函数原型:int pthread_join(pthread_t thread, void **ret);

函数参数:

        thread -- 等待线程的id

        ret -- 输出型参数,获取线程函数的返回值

返回值:成功返回0,失败返回错误码

在代码3.1中, 线程函数threadRoutine中在堆区new了5个int型数据的空间,并赋值为1~5,线程函数返回指向这块堆区资源的指针,主线程等待子线程退出,主线程可以看到这块资源。注意线程函数返回值的类型为void*,使用返回值的时候要注意强制类型转换。

代码3.1:pthread_join线程等待

#include <iostream>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>void *threadRoutine(void *args)
{std::cout << (char*)args << std::endl;int *pa = new int[5];for(int i = 0; i < 5; ++i){pa[i] = i + 1;}return (void*)pa;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");int *pa = nullptr;// 等待线程退出,pa接收线程函数返回值pthread_join(tid, (void**)&pa);// 获取线程函数返回值指向的空间内的资源std::cout << "thread exit" << std::endl;for(int i = 0; i < 5; ++i){printf("pa[%d] = %d\n", i, pa[i]);}delete[] pa;return 0;
}
图3.1  代码3.1的运行结果

四. 线程的终止和分离

可以实现线程终止的方法有:

  • 线程函数return。
  • 由另一个线程将当前线程取消pthread_cancel。
  • 线程退出pthread_exit。

4.1 线程函数return

pthread_create函数的第三个参数start_routine为线程函数指针,新创建的线程就负责执行这个函数,如果这个函数运行完毕return退出,那么,线程就退出了。

但是这种方法对主线程不适用,如果主线程退出,就是进程终止了,全部线程都会退出

结论:如果线程函数return,那么线程就退出了,但主线程return进程就退出了,不适用这种退出方式。

线程函数接收一个void*类型的参数,返回void*类型参数,如果线程函数运行到了return,那么这个线程就退出了,如代码3.1中的threadRoutine,就是采用return来终止线程的。

代码4.1验证了主线程退出的情况,设定线程函数为死循环IO输出,但是主线程在创建完子线程sleep(2)之后return,发现线程函数并没有继续运行,证明了主线程退出不适用于return这种方法来终止。

代码4.1:验证主线程不能通过return退出

// 线程函数死循环
void *threadRoutine1(void *args)
{while(true){std::cout << (char*)args << std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;// 创建线程int n = pthread_create(&tid, nullptr, threadRoutine1, (void*)"thread 1");std::cout << "main thread" << std::endl;sleep(2);   // 主线程sleep 2s后退出return 5;
}
图4.1 代码4.1的运行结果

4.2 线程取消 pthread_cancel

pthread_cancel函数可用于通过指定线程id,来取消线程。

pthread_cancel -- 取消线程

函数原型:int pthread_cancel(pthread_t thread)

函数参数:thread -- 被取消的线程的id

返回值:成功返回0,不成功返回非0的错误码

一般而言,采用主线程取消子线程的方式来取消线程,一个线程取消自身也是可以的,但一般不会这样做,pthread_cancel(pthread_self()) 可用于某个线程取消其自身,其中pthread_self函数的功能是获取线程自身的id。

  • pthread_self函数 -- 获取线程自身的id。

如果一个线程被取消了,那么就无需在主线程中通过pthread_join对这个线程进行等待,但如果使用了pthread_join对被取消的线程进行等待,那么pthread_join的第二个输出型参数会记录到线程函数的返回值为-1。

结论:如果一个线程被pthread_cancel了,那么pthread_join会记录到线程函数返回(void*)-1。 

在代码4.2中,通过pthread_cancel函数,取消子线程,然后pthread_join等待子线程,输出强转为long long类型的返回值ret,记录到ret的值为-1。

代码4.2:取消子线程并等待取消了的子线程

// 线程函数
void *threadRoutine1(void *args)
{while(true){std::cout << (char*)args << std::endl;sleep(1);}return (void*)10;
}int main()
{pthread_t tid;// 创建线程pthread_create(&tid, nullptr, threadRoutine1, (void*)"thread 1");std::cout << "main thread" << std::endl;sleep(2);   pthread_cancel(tid);   // 取消id为tid的子线程void *ret = nullptr;int n = pthread_join(tid, &ret);    // 等待已经取消的线程退出std::cout << "ret : " << (long long)ret << std::endl;return 0;  
}
图4.2 代码4.2的运行结果

4.3 线程退出 pthread_exit

pthread_exit 函数在线程函数中,可用于指定线程函数的返回值并退出线程,与return的功能基本完全相同,注意,exit不可用于退出线程,在任何一个线程中调用exit,都在让整个进程退出。

pthread_exit 函数 -- 让某个线程退出

函数原型:void pthread_exit(void *ret

函数参数:ret -- 线程函数的退出码(返回值)

代码4.3在线程函数中调用pthread_exit终止线程,指定返回值为(void*)111,在主线程中等待子线程,并将线程函数返回值存入ret中,输出(long long)ret的值,证明子线程返回(void*)111。

代码4.3:通过pthread_exit终止线程

#include <iostream>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>// 线程函数
void *threadRoutine1(void *args)
{int count = 0;while(true){std::cout << (char*)args << ", count:" << ++count << std::endl;if(count == 3) pthread_exit((void*)111);sleep(1);}return nullptr;
}int main()
{pthread_t tid;// 创建线程pthread_create(&tid, nullptr, threadRoutine1, (void*)"thread 1");std::cout << "main thread" << std::endl;sleep(5);   void *ret = nullptr;pthread_join(tid, &ret);   std::cout << "[main thread] child thread exit, ret:" << (long long)ret << std::endl;return 0;  
}
图4.3 代码4.3的运行结果

4.4 线程分离 pthread_detach 

严格意义上讲,pthread_detach并不算线程退出即使一个线程函数中使用了pthread_detach(pthread_self())对其自身进行分离,线程函数在pthread_detach之后的代码也会正常被执行。

pthread_detach一般用于不需要关心退出状态的线程被pthread_detach分离的子线程,即使主线程不等待子线程退出,子线程也不会出现僵尸问题

一般来说,都是线程分离其自身,当然也可以通过主线程分离子线程,但不推荐这么做。

经pthread_detach分离之后的线程,不应当pthread_join等待,如果等待一个被分离的线程,那么pthread_join函数会返回错误码。

结论:(1).pthread_detach用于将不需要关系关系退出状态的子线程分离   (2).被分离的线程不应被等待,如果被等待,那么pthread_join会返回非0错误码。

代码4.4演示了经pthread_detach分离之后线程函数继续运行,等待被分离的线程失败的情景。

代码4.4:线程分离及等待被分离的线程

#include <iostream>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>// 线程函数
void *threadRoutine1(void *args)
{// 子线程将其自身分离pthread_detach(pthread_self());int count = 0;while(true){std::cout << (char*)args << ", count:" << ++count << std::endl;if(count == 3) pthread_exit((void*)111);sleep(1);}return (void*)10;
}int main()
{pthread_t tid;// 创建线程pthread_create(&tid, nullptr, threadRoutine1, (void*)"thread 1");std::cout << "main thread" << std::endl;sleep(5);   void *ret = nullptr;int n = pthread_join(tid, &ret);    // 等待已经取消的线程退出 if(n != 0)  // 检验是否等待成功{std::cout << "wait thread error -> " << strerror(n) << std::endl;}return 0;  
}
图4.4 代码4.4的运行结果

五. 总结

  • pthread_create函数可以创建子线程,关于线程的管理方法及属性字段,被记录在动态库里,线程id本质上就是地址空间共享区的某个地址。
  • 由于Linux在系统层面不严格区分进程和线程,CPU调用只认PCB,因此为了保证每个线程栈空间的独立性,子线程的栈由用户层(动态库)提供,主线程的栈区就是地址空间的栈区。
  • 在多线程中,任何一个线程出现异常,影响范围都是整个进程,如果在某个线程中调用exec系列函数替换程序,那么整个进程都会被替换掉。
  • pthread_join的功能为在主线程中等待子线程,如果子线程没有被detach且不被主线程等待,那么子线程就会出现僵尸问题。
  • 有三种方法可以终止线程:(1). 线程函数return,这种方法不适用于主线程。(2). pthread_exit 函数终止线程函数。(3). pthread_cancel 取消线程,被取消的线程不需要被等待,如果等待会记录到线程函数返回(void*)-1。
  • 如果某个子线程的退出状态不需要关心,那么就可以通过pthread_detach分离子线程,分离后的线程不应被等待,如果被等待,那么pthread_join函数就会返回非零错误码。

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

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

相关文章

QML Book 学习基础3(动画)

目录 主要动画元素 例子&#xff1a; 非线性动画 分组动画 Qt 动画是一种在 Qt 框架下创建交互式和引人入胜的图形用户界面的方法&#xff0c;我们可以认为是对某个基础元素的多个设置 主要动画元素 PropertyAnimation-属性值变化时的动画 NumberA…

【操作系统】聊聊局部性原理是如何提升性能的

对于目前数据主导的系统&#xff0c;大多数都是Java/Go 技术栈MySQL&#xff0c;但是随着时间的推移&#xff0c;数据库数据的数据量过多&#xff0c;并且会频繁访问热点数据&#xff0c;为了提升系统的性能&#xff0c;一般都是加入缓存中间件、Redis。 局部性原理 我们知道…

springboot小知识:配置feign服务超时时间

背景&#xff1a;当前项目通过feign服务调用了其他两个项目的接口&#xff0c;但是由于特殊需求&#xff0c;需要调整某一个项目的feign服务的默认超时时间&#xff1a; 默认连接超时10秒&#xff0c;默认读取超时时间 60秒 1.找到定义的FeignClient 2.根据FeignClient定义的名…

创建python环境——Anaconda

在Windows中安装Anaconda和简单使用 一.Anaconda发行概述 Anaconda是一个可以便捷获取和管理包&#xff0c;同时对环境进行统一管理的发行版本&#xff0c;它包含了conda、 Python在内的超过180个科学包及其依赖项。 1.Anaconda发行版本具有以下特点&#xff1a; (1)包含了…

Redis笔记——(狂神说)

Nosql概述 为什么要用NoSql&#xff1f; 1、单机mysql的年代&#xff1a;90年代&#xff0c;网站访问量小&#xff0c;很多使用静态网页html写的&#xff0c;服务器没压力。 当时瓶颈是&#xff1a;1)数据量太大一个机器放不下。2)数据的索引(BTree)&#xff0c;一个机器内存也…

京东商品详情数据(H5端,PC端)高效对接第三方API接口

利用电商API获取数据的步骤 1.申请API接口注册Key和secret接入&#xff1a;首先要在相应电商平台上注册账号并申请API接口。 2.获取授权&#xff1a;在账号注册成功后&#xff0c;需要获取相应的授权才能访问电商API。 3.调用API&#xff1a;根据电商API提供的请求格式&…

【Spring】什么是 AOP(面向切面编程) ? 为什么要有 AOP ? 如何实现 Spring AOP ?

文章目录 前言一、什么是 AOP ?二、为什么要使用 AOP ?三、 AOP 的组成四、Spring AOP 的实现1, 添加依赖2, 定义切面3, 定义切点4, 定义通知5, 创建连接点 总结 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: &#x1f4d5; JavaSE基础: 基础语法…

Excel:如何实现分组内的升序和降序?

一、POWER 1、构建辅助列D列&#xff0c;在D2单元格输入公式&#xff1a; -POWER(10,COUNTA($A$2:A2)3)C2 2、选中B1:D10&#xff0c;注意不能宣导A列的合并单元格&#xff0c;进行以下操作&#xff1a; 3、删除辅助列即可 二、COUNTA 第一步&#xff0c;D2建立辅助列&#xf…

【开个空调】语音识别+红外发射

废话少说&#xff0c;直接上空调板子&#xff1a;YAPOF3。红外接收发射模块用的某宝上发现的YF-33(遗憾解码还没搞清楚&#xff0c;不然做个lirc.conf功能才多)。最后是语音识别用的幻尔的&#xff0c;某宝自然也有&#xff0c;它是个i2c的接口。 本篇胡说八道其实纯粹为了留个…

Nacos安装

一、下载Nacos1.4.1二、单机版本安装 2.1 将下载的nacos安装包传输到服务器2.2 解压文件2.3 进入bin目录下 单机版本启动2.4 关闭nacos2.5 访问Nacos地址 IP&#xff1a;8848/nacos三、集群版本的安装 3.1 复制nacos安装包&#xff0c;修改为nacos8849&#xff0c;nacos8850&am…

android手机销售app(IDEA,SpringBoot,SSM,MySQL)+支付宝支付+全套视频教程

本项目亮点: 支付宝支付 eCharts柱状图图表数据统计 【项目功能介绍】 本系统包含后台管理和前端app双端系统&#xff0c;后台管理的功能包含: 登录, 退出, 修改管理员信息(基本信息与头像),资源管理,角色管理,资源权限分配,字典管理,用户管理,图书管理,订单管理,订单统计; a…

常见的下载方式

一. 使用 window.open() 使用场景 // 1. 先封装一个实习下载的函数 export const download (path) > {window.open(下载的接口&#xff0c;例如&#xff1a;/fs/download?path path) } // 2. 使用&#xff1a;在需要下载的地方调用download函数&#xff0c;传入下载的u…

AP5192 DC-DC降压恒流LED汽车头灯摩托车电动车大灯电源驱动

AP5192是一款PWM工作模式,高效率、外围简单、 内置功率MOS管&#xff0c;适用于4.5-100V输入的高精度 降压LED恒流驱动芯片。最大电流1.5A。 AP5192可实现线性调光和PWM调光&#xff0c;线性调光 脚有效电压范围0.55-2.6V. AP5192 工作频率可以通过RT 外部电阻编程 来设定&…

Zebec Protocol 与 PGP 深度合作,将流支付更广泛的应用于薪资支付领域

随着传统机构的入局&#xff0c;以及相关加密合规法规的落地&#xff0c;加密支付正在成为一种备受欢迎的全新支付方式。加密支付基于区块链底层&#xff0c;不受地域、时间等的限制&#xff0c;能够实时到账&#xff0c;具备去中心化、非许可等特性。 流支付是一种具备创新性的…

【Flutter】Flutter 使用 fluttertoast 实现显示 Toast 消息

【Flutter】Flutter 使用 fluttertoast 实现显示 Toast 消息 文章目录 一、前言二、安装和基础使用三、不同平台的支持情况四、如何自定义 Toast五、在实际业务中的应用六、完整的业务代码示例&#xff08;基于 Web 端&#xff09;七、总结 一、前言 在这篇文章中&#xff0c;…

异步迭代器

一、什么是异步迭代器&#xff1f; 实现了 __aiter__() 和 __anext__() 方法的对象。__anext__ 必须返回一个 awaitable对象。async for 会处理异步迭代器的 __anext__() 方法所返回的可等待对象&#xff0c;直到其引发一个 StopAsyncIteration 异常。 二、实例 class Async…

QT 相关设置

目录 1.安装QT2.安装好之后需要做一些设置2.1 基本的字体及主题设置2.2 格式化美化代码插件设置 1.安装QT 具体教程不写了 2.安装好之后需要做一些设置 2.1 基本的字体及主题设置 进入选项 选择喜欢的主题 字号字体设置 2.2 格式化美化代码插件设置 先下载一个格式化插…

适配器设计模式

目录 一、适配器模式1.类适配器模式2.对象适配器模式3.接口适配器 二、适配器模式应用场景三、适配器模式的优缺点 一、适配器模式 B站&#xff1a;java架构师 定义&#xff1a;适配器模式把一个类的接口变换成客户端所期待的另一种接口&#xff0c;从而使原本因接口不匹配而…

Mysql 事物与存储引擎

MySQL事务 MySQL 事务主要用于处理操作量大&#xff0c;复杂度高的数据。比如说&#xff0c;在人员管理系统中&#xff0c; 要删除一个人员&#xff0c;即需要删除人员的基本资料&#xff0c;又需要删除和该人员相关的信息&#xff0c;如信箱&#xff0c; 文章等等。这样&#…

黑马点评环境搭建导入

一开始配置maven的时候&#xff0c;发现怎么都无法查看maven的版本&#xff0c;后来才知道是JAVA_HOME的问题&#xff0c;开头多了一个空格&#xff08;因为我是直接复制过去的&#xff09;&#xff0c;然后搜网上通过命令行可以看到肉眼看不到的bug。 通过命令行的方式改正确后…