【Hello Linux】线程控制

作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:简单介绍linux中的线程控制

线程控制

  • 线程创建
  • 线程等待
  • 线程终止
  • 线程分离
  • 线程id和进程地址空间布局

线程创建

我们可以通过下面pthread_create函数来创建一个新的线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

返回值说明:

  • 线程创建成功返回0 失败返回错误码

这里值得注意的是在线程库中 几乎所有的返回值都是成功返回0 失败返回错误码

参数说明:

  • thread:获取创建成功的线程ID 该参数是一个输出型参数
  • attr: 用于设置创建线程的属性 如果我们传入NULL则设置为默认属性
  • start_routine:这是一个函数地址 传入我们想要这个线程执行的函数
  • arg: 传给线程例程的参数 (默认是void* 类型 记得类型强转不然会报警告)

下面是代码示例

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <pthread.h>
    4 #include <unistd.h>
    5 
    6 void* run_thread(void* args)
    7 {
W>  8   char* msg = (char*)args;
    9   while(1)
   10   {
   11     printf("im a new pthread my tid is: %lu\n" , pthread_self());
   12     sleep(1);
   13   }
   14 }
   15 
   16 
   17 int main()
   18 {
   19   pthread_t tid;
   20   pthread_create(&tid ,NULL ,run_thread ,(void*)"thread 1");
   21   while(1)
   22   {
   23     printf("im main thread i create the new pid is:%lu\n", tid);
   24     sleep(1);
   25   }
   26   return 0;
   27 }       

上面代码的意思是我们创建一个子线程 这个线程不停的打印参数发送过去的消息 同时我们的主进程不停的打印另外的信息 同时我们调用了一个叫做pthread_self()的函数
在这里插入图片描述

但是这里我们发现我们进行编译之后会出现这样子的情况 这是因为我们找不到库文件所导致的

具体的原因可以参考我动静态库这篇博客

动静态库

所以说我们想要编译成功这个文件需要先指令连接的库文件

在这里插入图片描述

演示效果如下

在这里插入图片描述

我们可以发现新线程的pid就是我们主进程创建的pid

并且这个程序在同时执行两个死循环 这在我们之前的单执行流程序中是很难做到的

在这里我们还可以演示下线程健壮性不够强的特点 具体思路如下

我们在创建的新线程的代码中休眠三秒钟 并且故意造成一个野指针越界的问题

之后看看主进程会不会崩溃

具体代码表示如下

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <pthread.h>
    4 #include <unistd.h>
    5 
    6 void* run_thread(void* args)
    7 {
W>  8   char* msg = (char*)args;
    9   while(1)
   10   {
   11     printf("im a new pthread my tid is: %lu\n" , pthread_self());
   12     sleep(3);
   13     int* p = NULL;
   14     *p = 20;                                                                                                             
   15   }
   16 }
   17 
   18 
   19 int main()
   20 {
   21   pthread_t tid;
   22   pthread_create(&tid ,NULL ,run_thread ,(void*)"thread 1");
   23   while(1)
   24   {
   25     printf("im main thread i create the new pid is:%lu\n", tid);
   26     sleep(1);
   27   }
   28   return 0;
   29 }

线程等待

我们在学进程控制的时候讲过要进行进程等待 不然的话会造成僵尸进程的问题

同样的我们的线程也是通过创建PCB来保存数据 所以说线程也需要进程线程等待 不然的话会造成类似于僵尸线程的问题

在Linux操作系统中我们可以使用pthread_join函数来进行线程等待

pthread_join函数的函数原型如下:

int pthread_join(pthread_t thread, void **retval);

返回值说明:

  • 线程等待成功返回0 失败返回错误码

参数说明

  • thread:被等待线程的ID
  • retval:线程退出时的退出码信息 其中因为线程退出的返回值是一个void*类型的数据 所以我们要使用一个void * *类型的数据去接受它

调用该函数的线程将挂起等待 直到ID为thread的线程终止

下面是代码演示

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <pthread.h>
    4 #include <unistd.h>
    5 
W>  6 void* run_thread(void* args)
    7 {
    8   printf("im new thread my tid is:%lu\n",pthread_self());
    9   sleep(3);
   10   return (void*)101;
   11 }
   12 
   13 
   14 int main()
   15 {
   16   pthread_t tid;
   17   pthread_create(&tid ,NULL ,run_thread ,(void*)"thread 1");
   18   void* status = NULL;                                                                                                   
   19   printf("waiting ...\n");
   20   pthread_join(tid ,&status);
   21   printf("wait success , exit code is:%d\n", (int)(intptr_t)status);
   22   return 0;
   23 }

解释下上面这段代码

我们首先创建一个新线程 这个线程会在打印自己的线程tid之后休眠三秒并结束

最后我们主线程使用join函数接受它的返回值并打印

这里需要注意的是 我们并不能直接打印status 而是要将它强制类型转化为intptr_t再强制转化为int

演示结果如下

在这里插入图片描述

我们发现join函数确实可以接收我们的返回值

异常情况需要处理吗?

我们都知道一段程序运行有三种情况

  • 运行成功 结果正确返回退出码
  • 运行成功 结果错误返回退出码
  • 运行异常

那么的join需要处理异常的这种情况吗?

答案显然是不需要的 我们之前在解释健壮性的时候已经说过了 如果一个线程出现异常情况 那么操作系统就会发信号给进程从而杀死进程 那么这个时候我们进程接受异常也就没有意义了

Tip: 这里还需要注意的一点是如果我们创建了多个线程 我们等待只能使用for循环一个个等待

线程终止

我们在上面的线程等待代码中已经学习了第一个线程终止的方案 函数中return

下面是return的两种情况

  • main函数return代表主线程和进程退出
  • 其他线程函数return只代表线程return

pthread_exit

我们除了使用return之外还可以使用pthread_exit函数来终止一个线程 它的函数原型如下

  void pthread_exit(void *retval);

返回值说明:

我们这里退出的返回值是void 这是很合理的

因为一旦退出之后这个线程就不存在了 接受返回值也根本没有意义

参数说明:

  • retval:线程退出时的退出码信息

下面是代码演示

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <pthread.h>
    4 #include <unistd.h>
    5 
W>  6 void* run_thread(void* args)
    7 {
    8   printf("im new thread my tid is:%lu\n",pthread_self());
    9   sleep(3);
   10   pthread_exit((void*)101);                                                                              
   11 }
   12 
   13 
   14 int main()
   15 {
   16   pthread_t tid;
   17   pthread_create(&tid ,NULL ,run_thread ,(void*)"thread 1");
   18   void* status = NULL;
   19   printf("waiting ...\n");
   20   pthread_join(tid ,&status);
   21   printf("wait success , exit code is:%d\n", (int)(intptr_t)status);
   22   return 0;
   23 }

我们这里使用了thread_exit函数来进行线程退出 退出码是void* 类型的101

下面是运行结果

在这里插入图片描述

pthread_cancel函数

我们还可以使用cancel函数来取消一个线程 它的函数原型如下

int pthread_cancel(pthread_t thread);

返回值说明:

  • 线程等待成功返回0 失败返回-1

参数说明:

  • thread:被取消线程的ID

注意! 这个函数有许多种用法 我们既可以让线程取消自己 也可以让新线程取消主线程 还可以让主线程取消新线程 但是我们只建议使用主线程去取消新线程

下面是演示代码

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <pthread.h>
    4 #include <unistd.h>
    5 
W>  6 void* run_thread(void* args)
    7 {
    8   printf("im new thread my tid is:%lu\n",pthread_self());
    9   while(1)
   10   {
   11     sleep(3);
   12   }
   13 }
   14 
   15 
   16 int main()
   17 {
   18   pthread_t tid;
   19   pthread_create(&tid ,NULL ,run_thread ,(void*)"thread 1");
   20   void* status = NULL;
   21   printf("waiting ...\n");
   22   pthread_cancel(tid);
   23   pthread_join(tid ,&status);
   24   printf("wait success , exit code is:%d\n", (int)(intptr_t)status);
   25   return 0;                                                                                                                                                 
   26 }

我们在创建线程之后直接取消之并且查看退出码

演示结果如下

在这里插入图片描述

我们可以发现退出码确实是-1

为什么退出码是-1呢?

我们可以在系统中搜索“PATHREAD_CANCELED” 这个宏定义
在这里插入图片描述
我们可以发现实际上它就是被定义为了 ((void*)-1)

所以说我们以后在线程中看到返回值为-1就应该明白是这个线程被取消了

线程分离

在进程控制这一章节中 我们除了讲解进程创建 等待 终止之外还讲解了一个进程替换

但是在线程中进程替换是没有意义且不被允许的

因为我们说过线程和进程是共用的同一个虚拟地址空间 它分享一部分数据和代码 如果我们修改了这些代码和数据那么整个虚拟地址空间都会受到影响 所以说线程替换是不被允许的

而我们在线程控制这部分学习的是线程分离 我们可以使用pthread_detach函数来进行线程分离 它的函数原型如下

int pthread_detach(pthread_t thread);

我们线程分离之后实际上该线程还是使用的原来的进程地址空间 如果该线程崩溃了 那么进程也会崩溃

那么我们为什么还要进行线程分离呢?

因为线程分离之后我们就不需要线程等待了 就能节约等待的时间

与此同时我们的等待函数也将会失效 不能对于这个线程进行等待操作

返回值说明:

  • 线程分离成功返回0 失败返回错误码

参数说明:

  • thread:被分离线程的ID

下面是代码演示

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <pthread.h>
    4 #include <unistd.h>
    5 
W>  6 void* run_thread(void* args)
    7 {
    8   printf("im new thread my tid is:%lu\n",pthread_self());
    9   printf("hello im new thread\n");
   10   sleep(3);                                                                            
W> 11 }
   12 
   13 
   14 int main()
   15 {
   16   pthread_t tid;
   17   pthread_create(&tid ,NULL ,run_thread ,(void*)"thread 1");
   18   printf("waiting ...\n");
   19   pthread_detach(tid);
   20   sleep(3);
   21   return 0;}

我们在创建完这个线程之后就分离它 并在三秒之后终止主线程和分离的线程

演示结果如下

在这里插入图片描述

线程id和进程地址空间布局

我们可以通过下面的代码来查看主线程的线程id

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <pthread.h>
    4 #include <unistd.h>
    5 
    6 
    7 int main()
    8 {
    9   while(1)
   10   {
W> 11     printf("main thread id is:%x\n",pthread_self());                                   
   12     sleep(1);
   13   }
   14   return 0;
   15 }

演示结果如下

在这里插入图片描述

此外我们还可以创建一个新线程来打印它的tid

代码如下

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 #include <pthread.h>
    4 #include <unistd.h>
    5 
W>  6 void* thread_run(void* args)
    7 {
    8   while(1)
    9   {
W> 10     printf("im new thread my tid is:%x\n", pthread_self());
   11     sleep(1);
   12   }
   13 }                                                                                                                        
   14 
   15 int main()
   16 {
   17   pthread_t tid;
   18   pthread_create(&tid , NULL ,thread_run,(void *) "hello world" );
   19   while(1)
   20   {
W> 21     printf("main thread id is:%x\n",pthread_self());
   22     sleep(1);
   23   }
   24   return 0;
   25 }
  ~

演示结果如下

在这里插入图片描述

我们这里直接交代结论:

我们这里查看所有线程id它都是一个内存中的虚拟地址

我们通过ldd指令可以查看到

在这里插入图片描述
我们连接的线程库实际上就是一个动态库 它既然是个动态库 那么它肯定就是一个文件

在这里插入图片描述

当这个文件被加载到物理内存之后会经过页表的映射加载到进程地址空间中的共享区

我们说每个进程都有自己栈空间 实际上这个栈空间和我们所理解的栈空间是不一样的

线程采用的栈就是在共享区中开辟的 除此之外线程还有自己的struct pthread 当中包含了对应线程的各种属性

每个线程还有自己的线程局部存储 当中包含了对应线程被切换时的上下文数据

我们每增加一个线程在共享区中就会增加一个这样子的结构体

在这里插入图片描述

上面我们所用的各种线程函数 本质都是在库内部对线程属性进行的各种操作 最后将要执行的代码交给对应的内核级LWP去执行就行了 也就是说线程数据的管理本质是在共享区的

而LWP就存储在线程的struct pthread中

所以说我们看到的这些tid实际上就是共享区的虚拟地址

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯基础14:BASIC-1试题 闰年判断

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给定一个年份&#xff0c;判断这一年是不是闰年。 当以下情况之一满足时&#xff0c;这一年是闰年&#xff1a; 1. 年份…

Java面向对象 - 封装、继承和多态的综合练习(答案+知识点总结)第1关:封装、继承和多态进阶(一)+ 第2关:封装、继承和多态进阶(二)

目录 第1关&#xff1a;封装、继承和多态进阶&#xff08;一&#xff09; 报错总结 & 注意事项&#xff1a; 第2关&#xff1a;封装、继承和多态进阶&#xff08;二&#xff09; 源码&#xff1a; 报错总结 & 注意事项&#xff1a; 思维导图免费制作网站&#xf…

软考软件设计师下午试题一

数据流图基本图形元素 外部实体 外部系统是当前系统之外的系统 数据存储 在里面存储数据后还能取出来用 跟实体没有关系&#xff0c;他负责存储加工的数据或者提供数据给加工 加工 灰洞的解释比如输入需要两个才能得到输出的&#xff0c;但是他只输入了一个就是灰洞 数…

Matlab傅里叶级数展开(附结果图)

Matlab傅里叶级数展开&#xff08;附结果图&#xff09; 代码下载链接 代码下载链接 代码下载链接 如下图所示&#xff1a;

“唯一靶点”的华堂宁会成控糖爆品吗?

一上市&#xff0c;两次“断货”的货华堂宁有爆品那味儿了。 2022年10月28日华领医药-B&#xff08;02552.HK&#xff09;公告华堂宁&#xff08;多格列艾汀&#xff09;正式进入商业化&#xff0c;一周后各个渠道便进入到了断货和限售的状态。 对于一个不在传统九大降糖药品…

元宇宙与网络安全

元宇宙是一种虚拟现实空间&#xff0c;用户可以在计算机生成的环境中进行互动。元宇宙的应用范围很广&#xff0c;比如房地产&#xff0c;医疗&#xff0c;教育&#xff0c;军事&#xff0c;游戏等等。它提供了更具沉浸感的体验&#xff0c;更好地现实生活整合&#xff0c;以及…

组件、套件、 中间件、插件

组件、套件、 中间件、插件 组件 位于框架最底层&#xff0c;是由重复的代码提取出来合并而成。组件的本质&#xff0c;是一件产品&#xff0c;独立性很强&#xff0c;组件的核心&#xff0c;是复用&#xff0c;与其它功能又有强依赖关系。 模块 在中台产品和非中台产品中&…

C语言程序环境和预处理

文章目录程序的翻译环境和执行环境详解编译和链接翻译环境编译本身也分为几个阶段预处理编译汇编链接段表符号表的合并预处理详解预定义符号#define#define 定义标识符#define定义宏#define替换规则#和#### 的作用带副作用的宏参数宏和参数的对比宏和函数的一个对比命名约定#un…

FastestDet:比yolov-fastest更快!更强!全新设计的超实时Anchor-free目标检测算法

本篇文章转自于知乎——qiuqiuqiu,主要设计了一个新颖的轻量级网络! 代码地址:https://github.com/dog-qiuqiu/FastestDet 1 概述 FastestDet是设计用来接替yolo-fastest系列算法,相比于业界已有的轻量级目标检测算法如yolov5n, yolox-nano, nanoDet, pp-yolo-tiny, Fast…

CSS基础知识,必须掌握!!!

CSS基础知识Background&#xff08;背景&#xff09;CSS文本格式文本颜色文本对齐格式文本修饰文本缩进CSS中的字体字体样式字体大小CSS链接&#xff08;link&#xff09;CSS列表不同列表标项CSS列表项用图片作为标记CSS列表标记项位置CSS中表格&#xff08;table&#xff09;表…

Shell脚本之嵌套循环与中断跳出

1、双重循环 1.1 格式 #!/bin/bash for ((i9;i>1;i--)) do for ((j9;j>$i;j--)) do echo -n -e "$j$i$[$i*$j]\t" done echo done1.2 实例操作 2.1 格式 #!/bin/bash for ((a1;a<9;a)) dofor ((b9;b>a;b--))doecho -n " "donefor((c1;c<…

系统信息:uname,sysinfo,gethostname,sysconf

且欲近寻彭泽宰&#xff0c;陶然共醉菊花怀。 文章目录系统信息系统标识 unamesysinfo 函数gethostname 函数sysconf()函数系统信息 系统标识 uname 系统调用 uname()用于获取有关当前操作系统内核的名称和信息&#xff0c;函数原型如下所示&#xff08;可通过"man 2 un…

面向对象编程(基础)7:再谈方法(重载)

目录 7.1 方法的重载&#xff08;overload&#xff09; 7.1.1 概念及特点 7.1.2 示例 举例1&#xff1a; 举例2&#xff1a; 举例3&#xff1a;方法的重载和返回值类型无关 7.1.3 练习 **练习1&#xff1a;** 练习2&#xff1a;编写程序&#xff0c;定义三个重载方法并…

如何大批量扫描的发票进行ocr识别导出Excel表格和WPS表格

OCR技术&#xff1a;OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;是将数字图像中的文字识别成字符代码的技术&#xff0c;在发票识别中应用广泛。通过OCR技术&#xff0c;可以将图片发票上的信息识别出来&#xff0c;并导出到Excel表格中…

3年测试越来越迷茫... 技术跟不上接下来是不是要被淘汰了?

这两天和朋友聊到了软件测试的发展&#xff1a;这一行的变化确实蛮大&#xff0c;从开始最基础的功能测试&#xff0c;到现在自动化、性能、安全乃至于以后可能出现的大数据测试、AI测试岗位需求逐渐增多。我也在软件测试这行摸爬滚打有些日子了&#xff0c;正好有朋友问我&…

晶振01——晶振分类和无源晶振的设计

晶振 晶振相当于人的心脏&#xff0c;能跳动&#xff0c;整个系统才是“活的”。 晶振常见有有源晶振、无源晶振。 有源晶振比较贵&#xff0c;但是需要外围电路少&#xff0c;供个电就能工作。 无源晶振价格便宜&#xff0c;匹配电路复杂些。 以无源晶振进行分析&#xff0c…

WCF手麻系统源码,手术室麻醉临床系统源代码,商业源码 有演示

手麻系统源码 手术麻醉系统源码 手术室麻醉临床信息系统源码 商业级源码&#xff0c;有演示&#xff0c;三甲医院临床应用多年&#xff0c;系统稳定。 文末获取联系&#xff01; 技术架构&#xff1a;C# .net 桌面软件 C/S版&#xff0c;前后端分离&#xff0c;仓储模式 开发语…

2.5.3 乘法

这段话告诉我们&#xff0c;在程序中有一条乘法运算语句。这个程序会让计算机帮助我们完成一个简单的数学问题&#xff1a;计算6乘以2。和我们平常做数学题一样&#xff0c;程序使用*号表示乘法运算。语句 “feet 6 * fathoms;” 可以这样理解&#xff1a;它会找到之前我们定义…

spring 随笔 async 1-源码走读

0. 这一块比较简单&#xff0c;还就内个无障碍阅读 不谈,放个调用栈的日志先 … // 我们自己写的 Async 注解的方法 simpleTest:31, TestAsyncTestService (cn.angel.project.angelmicroservicesample.test.service) invoke:-1, TestAsyncTestService$$FastClassBySpringCGLI…

手撕vector

文章目录一.vector的基本结构二.构造函数调用不明确三.迭代器失效&#xff08;其实是野指针问题&#xff09;a.扩容导致的迭代器失效b.意义不同四.深层次的深浅拷贝五.整体代码实现有了前面模拟实现string的经验&#xff0c;vector对大家来说也不会算很难。vector本质也就是一个…