11. 线程

11. 线程

  • 1. 线程概述
    • 1.1 线程概念
      • 1.1.1 什么是线程
      • 1.1.2 线程是如何创建起来的
      • 1.1.3 线程的特点
      • 1.1.4 线程与进程
    • 1.2 并发和并行
  • 2. 线程 ID
  • 3. 创建线程
  • 4. 终止线程
  • 5. 回收线程
  • 6. 取消线程
    • 6.1 取消一个线程
    • 6.2 取消状态以及类型
    • 6.3 取消点
    • 6.4 线程可取消性的检测
  • 7. 分离线程
  • 8. 注册线程清理处理函数
  • 9. 线程属性
    • 9.1 线程栈属性
    • 9.2 分离状态属性
  • 10. 线程与信号
    • 10.1 信号如何映射到线程
    • 10.2 线程的信号掩码
    • 10.3 向线程发送信号
    • 10.4 异步信号安全函数

1. 线程概述

1.1 线程概念

1.1.1 什么是线程

线程是参与系统调度的最小单位,它被包含在进程之中,是进程中的实际运行单位。一个线程指的是进程中的一个单一顺序的控制流,或者说是执行流,一个进程中可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务。

1.1.2 线程是如何创建起来的

当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程,因为它是程序一开始就运行的线程。应用程序通常是以 main() 作为入口开始运行的,所以 main() 就是主线程的入口函数。任何一个进程都包含一个主线程,只有主线程的进程称为单线程进程,多线程的其它线程通常是由主线程调用 pthread_create 创建的。主线程通常会在最后结束运行,执行各种清理工作。

1.1.3 线程的特点

线程是程序最基本的运行单位,真正运行的是进程中的线程。当启动应用程序后,系统就创建了一个进程,可以认为进程仅仅是一个容器,它包含了线程运行所需的数据结构、环境变量等信息。同一进程中的多个线程将共享该进程中的全部系统资源,但同一进程中的多个线程有各自的调用栈,自己的寄存器环境、自己的线程本地存储。
线程的特点如下:

  • 线程不单独存在,而是包含在进程中;
  • 线程是参与系统调度的基本单位;
  • 可并发执行。
  • 共享进程资源

1.1.4 线程与进程

进程也可以创建多个进程来并发执行,但是进程间切换开销大,而且进程间通信较为麻烦。线程创建的速度远大于进程创建的速度,多线程在多核处理器上更有优势。

1.2 并发和并行

串行是指必须完成上一个任务才能去做下一个任务;
并行是指多个任务可以同时执行;
并发强调的是时分复用,是指可以打断当前执行的任务而去执行另一个任务

2. 线程 ID

进程 ID 在整个系统中都是唯一的,但线程 ID 只有在它所属的进程上下文中才有意义。

#include <pthread.h>
pthread_t pthread_self();		// 获取自己的线程 ID
int pthread_equal(pthread_t t1,pthread_t t2);	// 检查两个线程 ID 是否相等,相等返回非0,否则返回0

3. 创建线程

#include <pthread.h>
int pthread_create(pthread_t *thread,const pthread_attr_t *attr, void*(*start_routine)(void*),void*arg);
/** thread:函数成功返回时,新创建的线程 ID 会保存在thread指向的内存中* attr:指向一块缓冲区,该缓冲区定义里线程的各种属性,如果是NULL,表示以默认属性* start_routine:函数指针,指向一个函数,新创建的线程从该函数开始运行。返回值和参数都为void*,参数就是第四个参数arg* arg:传递给函数的参数。一般情况下,需要将arg指向一个全局或堆变量,也就是说在线程的生命周期内,该参数必须存在。也可以设置为NULL,表示不需要传参。*/
static void *routine(void *arg)
{cout << "新线程创建成功,进程 ID: "<<getpid()<<" 线程 ID: "<<pthread_self()<<endl;return (void*)0;
}
int main()
{pthread_t tid;pthread_create(&tid,NULL,routine,NULL);cout << "我是主线程,进程ID: "<<getpid()<<" 线程 ID: "<< pthread_self()<<endl;sleep(1);return 0;
}

休眠 1 秒是因为,如果主线程不进行休眠,它就可能立马退出,新线程可能就没有机会运行
运行之后可以发现,两个线程的进程 ID 都是一样的,但是线程 ID 不同

4. 终止线程

  • 在线程的执行函数中调用 return 返回,返回值就是线程的退出码
  • 线程调用 pthread_exit()
  • 调用 pthread_cancel() 取消线程
    在进程的任意位置调用 exit() 或 _exit() 都会导致进程终止
#include <pthread.h>
void pthread_exit(void *retval);

线程退出码可由另一个线程调用 pthread_join() 获取。但是要注意,retval 不能分配在线程栈中,因为不能保证该空间是否有效。

5. 回收线程

#include <pthread.h>
int pthread_join(pthread_t thread,void **retval);
/* * thread:需要等待的线程* retval:如果不为NULL,将线程的退出状态保存在*retval中。如果目标线程被取消,将PTHREAD_CANCELED保存。如果对退出状态不关系,可以设置为NULL*/

该函数会阻塞式等待指定的线程终止。如果该线程已经终止,则立即返回。如果多个线程同时调用,那么结果是不确定的。
若线程未分离(detached),则必须使用该函数来等待线程终止,回收线程资源;如果线程终止后,没有其他线程调用该函数回收该线程,那么该线程将变成僵尸线程。如果僵尸进程过多,会导致系统无法创建新的线程,但是进程终止后,进程会被其父进程回收,所以僵尸进程同样也会被回收。
pthread_join() 和 waitpid() 的区别在于:

  • 线程之间的关系是对等的。进程中的任意线程均可调用 pthread_join() 来等待另一个线程终止。但是父进程如果使用 fork() 创建了子进程,那么它也是唯一能够对子进程调用 wait() 的进程
  • 不能以非阻塞方式调用 pthread_join(),但是 waitpid() 可以实现非阻塞方式等待,也可以是阻塞式等待。

6. 取消线程

向一个线程发送一个信号,要求它立刻退出,就叫做取消线程。

6.1 取消一个线程

#include <pthread.h>
int pthread_cancel(pthread_t thread);

发出取消请求后,函数立即返回,不会等待目标线程的退出。默认情况下,目标线程也会立即退出。但是,线程可以设置自己不被取消或者控制如何被取消,所以该函数并不会等待线程终止,仅仅是提出请求。

6.2 取消状态以及类型

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
// 如果对线程取消之前的状态及类型不感兴趣,可以将第二个参数设置为NULL
/* state:* PTHREAD_CANCEL_ENABLE:线程可以取消,也是新创建线程取消状态的默认值* PTHREAD_CANCEL_DISABLE:线程不可以被取消,接收到取消请求后会将请求挂起,直到取消状态改变* /
/* type:* PTHREAD_CANCEL_DEFERRED:取消请求到来时,线程还是继续运行,取消请求被挂起,直到线程到达某个取消点,这也是默认类型* PTHREAD_CANCEL_ASYNCHRONOUS:可能会在任何时间点取消线程,一般不用。

如果线程的取消状态是默认的,那么对取消请求的操作取决于取决类型。当某个线程调用 fork() 创建子进程之后,子进程会继承调用线程的取消状态和取消类型,而当某线程调用 exec 函数时,会将新程序主线程的取消状态和类型设为默认值。

6.3 取消点

将线程的取消类型设置为默认时,收到其它线程发送来的取消请求时,仅当线程抵达某个取消点时,取消请求才会起作用。
取消点就是一些列函数,当执行到这些函数时,才会真正相应取消请求。在没有出现取消点时,取消请求是无法得到处理的,因为此时正在执行的代码不能被停止。
取消点可以使用man 7 pthreads查看

6.4 线程可取消性的检测

如果正在执行的是一个不含取消点的循环,可以使用 pthread_testcancel() 产生一个取消点,如果已有处于挂起状态的取消请求,那么只要调用该函数,线程就会终止。

#include <pthread.h>
void pthread_testcancel();

7. 分离线程

有时,不关心线程的返回状态,只希望系统在线程终止时能够自动回收线程资源并将其移除。

#include <pthread.h>
int pthread_detach(pthread_t thread);

一个线程可以将别的线程分离,也可以分离自己。一个线程一旦处于分离状态,就不能使用 pthread_join() 来获取终止状态,当其终止后,会自动回收线程资源。

8. 注册线程清理处理函数

与进程不同,一个线程可以注册多个清理函数,这些清理函数记录在栈中,每个线程都可以拥有一个清理函数栈,栈是一种先进后出的数据结构。

#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void*), void *arg);
void pthread_cleanup_pop(int execute);

当线程执行以下动作时,清理函数栈中的函数才会被执行:

  • 线程调用 pthread_exit() 退出时;
  • 线程响应取消请求时;
  • 用非 0 参数调用 pthread_cleanup_pop()
    除了以上三种情况之外,其他方式终止线程将不会执行线程清理函数。
    execute 如果是 0,清理函数不会被调用,只是将清理函数栈中最顶层的函数移除;如果非 0,会将函数执行,并清理。
    在使用时,必须在与线程相同的作用域中以匹配对的形式使用。
    有时,线程清理函数并不一定需要在线程退出时才执行,可以调用 pthread_cleanup_pop() 传入非 0,手动执行清理函数。

9. 线程属性

创建线程时,可以设置属性,当定义 pthread_attr_t 对象之后,需要使用 pthread_attr_init() 和 pthread_attr_destroy() 执行初始化和销毁工作。

#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

该结构体中的属性比较多,这里不详细介绍。Linux 为该结构体对象的每种属性提供了设置属性的接口以及获取属性的接口。

9.1 线程栈属性

#include <pthread.h>
int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr,size_t stacksize);
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr,size_t *stacksize);// 单独设置或获取大小地址等信息
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);

9.2 分离状态属性

#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
/* detachstate:* PTHREAD_CREATE_DETACHED:新建线程一开始就处于分离状态,无法被其他线程回收* PTHREAD_CREATE_JOINABLE:正常启动线程,可以被其他线程回收

10. 线程与信号

10.1 信号如何映射到线程

信号在一些方面是属于进程层面(由进程中的所有线程共享)的,而在另一些方面是属于单个线程层面的。

  • 信号的系统默认行为是属于进程层面的。当进程中任一线程收到任何一个未经处理(忽略或捕捉)的信号时,会执行该信号的默认操作,通常是停止或终止进程。
  • 信号处理函数属于进程层面,进程中的所有线程共享程序中所注册的信号处理函数
  • 信号的发送既可以针对整个进程,也可以针对某个特定的线程,符合以下任意一个时,是针对某个线程:
    • 产生了硬件异常相关信号,如 SIGBUS、SIGFPE、SIGILL 和 SIGSEGV 信号。这些硬件异常信号在某个线程执行指令的过程中产生,也就是说这些信号是由某些线程引起的,那么在这种情况下,系统会将信号发送给该线程
    • 当线程试图对已断开的管道进行写操作时所产生的 SIGPIPE 信号
    • 由函数 pthread_kill() 或 pthread_sigqueue() 所发出的信号,这些函数运行线程向同一进程下的其他线程发送指定的信号。
  • 当一个多线程进程接收到一个信号时,且该信号绑定了信号处理函数时,内核会任选一个线程来接收这个信号,让进程对单个信号重复接收没有意义
  • 信号掩码其实是属于线程层面的,对于一个多线程来说,各个线程可以调用 pthread_sigmask() 来设置它们各自的信号掩码。
  • 针对整个进程所挂起的信号,以及针对每个线程所挂起的信号,内核都会分别进行维护。

10.2 线程的信号掩码

#include <signal.h>
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

每个刚创建的线程,都会从创建者处继承信号掩码,这个新的线程可以通过该函数改变它的信号掩码

10.3 向线程发送信号

#include <signal.h>
int pthread_kill(pthread_t thread, int sig);

10.4 异步信号安全函数

应用程序中涉及信号处理函数时必须要非常小心,因为信号处理函数可能会在程序执行的任意时间点被调用,从而打断主程序。
前面介绍了线程安全函数,可以被多个线程同时调用,每次都能得到预期的结果,但是这里有前提条件,那就是不能在信号处理函数中调用。
异步信号安全函数(async-signal-safe function)指的是可以在信号处理函数中被安全调用的线程安全函数。可以使用 man 7 signal查看。
对于一个安全的信号处理函数,需要做到以下几点:

  • 首先确保信号处理函数本身的代码是可重入的,且只能调用异步信号安全函数
  • 当主程序执行不安全函数或是去操作信号处理函数也可能会更新的全局数据结构时,要阻塞信号的传递。

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

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

相关文章

【React系列】Hook(一)基本使用

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. 认识hook 1.1. 为什么需要hook Hook 是 React 16.8 的新增特性&#xff0c;它可以让我们在不编写class的情况下…

OpenFeign相关问题及答案(2024)

1、什么是OpenFeign&#xff0c;它如何简化远程服务调用&#xff1f; OpenFeign是一个声明式的Web服务客户端&#xff0c;它使得编写HTTP客户端变得更加容易。它属于Spring Cloud Netflix项目的一部分&#xff0c;可以与Spring Boot应用轻松集成。通过使用OpenFeign&#xff0…

群晖Docker部署HomeAssistant容器结合内网穿透远程控制家中智能设备

目录 一、下载HomeAssistant镜像 二、内网穿透HomeAssistant&#xff0c;实现异地控制智能家居 三、使用固定域名访问HomeAssistant 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站 Ho…

Guava Cache 异步刷新技巧,你值得拥有!

以下文章来源于勇哥Java实战 &#xff0c;作者勇哥 Guava Cache是一款非常优秀的本地缓存框架。 这篇文章&#xff0c;我们聊聊如何使用 Guava Cache 异步刷新技巧带飞系统性能 。 1 经典配置 Guava Cache 的数据结构跟 JDK1.7 的 ConcurrentHashMap 类似&#xff0c;提供了基…

citeSpace保姆级安装使用教程

citeSpace保姆级安装使用教程 文章目录 citeSpace保姆级安装使用教程CiteSpace功能与参数区安装使用知网数据导出citespace数据导入结果 设置操作隐藏节点 CiteSpace功能与参数区 安装 citeSpace安装教程 citespace下载 网址&#xff1a;https://citespace.podia.com/ 安装之…

大A又跌了

才开盘几天&#xff0c;又开始下跌了。生活更加苦难。期待高深算法。

18、Kubernetes核心技术 - InitContainer(初始化容器)

目录 一、概述 二、使用InitContainer 一、概述 InitContainer即初始化容器&#xff0c;是 K8S官方为我们提供的一个可以用来判断环境是否已经满足运行 Pod 应用前所需要的条件。 比如我们有一个应用&#xff0c;需要部署到Tomcat环境&#xff0c;那么在部署这个应用Pod之前…

STM32使用中断方式进行USART数据收发以及printf函数的重写

时间记录&#xff1a;2024/1/5 一、USART/UART介绍 协议介绍 &#xff08;1&#xff09;起始位&#xff0c;一位逻辑电平0表示 &#xff08;2&#xff09;数据位&#xff0c;8-9位&#xff0c;逻辑高低电平&#xff0c;一般使用8位 &#xff08;3&#xff09;校验位&#xff…

DevOps(5)

目录 21.如何在Linux下访问分区&#xff1f; 22.什么是硬链接&#xff1f; 23.Linux下文件名的最大长度是多少&#xff1f; 24.什么是以点开头的文件名&#xff1f; 25.解释虚拟桌面&#xff1f; 21.如何在Linux下访问分区&#xff1f; Linux在驱动器标识符的末尾分配数字…

如何获取时间戳?

获取现在的时间0时0秒 一、JavasCRIPT时间转时间戳 JavaScript获得时间戳的方法有五种&#xff0c;后四种都是通过实例化时间对象new Date() 来进一步获取当前的时间戳&#xff0c;JavaScript处理时间主要使用时间对象Date Date.now()可以获得当前的时间戳&#xff1a; con…

2-sql注入之sqli-labs靶场搭建

文章目录 SQL注入之sqli-labs靶场搭建1、Sqli-labs环境安装需要安装以下环境工具下载链接&#xff1a; 2、phpstudy连接mysql总是启动了又停止第一种情况可能是端口占用问题第二种情况就是曾经在电脑上安装过mysql SQL注入之sqli-labs靶场搭建 Sqli-labs是一个印度程序员写的&…

vmware中ubuntu虚拟机不能够用共享文件夹

有时候发现装好虚拟机后&#xff0c;然后 虚拟机-设置-选项-共享文件夹 然后使用快捷键ctrlaltt 打开命令行&#xff0c;cd /mnt下没有看到hgfs文件夹 解决办法是安装vmware tools工具 此时想通过点击 虚拟机-安装vmwaretools工具 按钮 居然发现该按钮是灰色的&#xff0…

CMake入门教程【核心篇】动态库(dll, so)

😈「CSDN主页」:传送门 😈「Bilibil首页」:传送门 😈「动动你的小手」:点赞👍收藏⭐️评论📝 文章目录 CMake入门教程【核心篇】动态库(dll, so)1.简介2.动态库的优势3.动态库的劣势4.创建动态库5.完整代码示例6.实战使用技巧与注意事项

实现文件拖拽上传的功能

1 先来看一下效果 2 我们来看一下代码执行的结果&#xff1a; 我们创建目标的容器盒子 和可以展示数据的ul 监听进入目前盒子的事件 3 文件进入目标容器中解析文件

Python中 的函数介绍

函数 在Python中,函数是用来执行某种功能的 函数定义 def function_name(参数列表):代码块[return val]比如 #---------------定义------------- def add_3(num):newnum num3return newnumnum 3 print(add_3(num))函数调用 def add_3(num):newnum num3return newnumnum …

科研上新 | 第6期:优化LLM数学推理;深度学习建模基因表达调控;基于深度学习的近实时海洋碳汇估算

编者按&#xff1a;欢迎阅读“科研上新”栏目&#xff01;“科研上新”汇聚了微软亚洲研究院最新的创新成果与科研动态。在这里&#xff0c;你可以快速浏览研究院的亮点资讯&#xff0c;保持对前沿领域的敏锐嗅觉&#xff0c;同时也能找到先进实用的开源工具。 本期内容速览 …

Namp扫描工具的使用

写在前面&#xff1a; Nmap是一款应用广泛的网络探测工具&#xff0c;它可以识别网络中计算机及操作系统、检测端口、识别协议指纹、检测文件传输以及执行安全扫描等功能&#xff0c;从而防止攻击行为的发生。 一、介绍 Nmap&#xff08;Network Mapper&#xff09;是一个网…

在 IDEA 中创建JavaWeb 项目的方式(超详细步骤教程和遇到的问题)

目录 0-1项目图片操作步骤链接0-2项目结构预览1.新建Project2.定义项目名称3.创建完成后项目结构4.创建config文件夹4.1 作用 5.在WEB-INF下创建lib文件夹5.1然后搞几个常用的jar包放入5.1.1jar包全选中后右键选择放入类库5.1.2jar包全选中后右键选择放入类库 6.创建src下文件夹…

软件测试金融项目经验总结,面试题都问什么?

1、APP端/客户端接口加解密介绍 加密方式&#xff08;两种&#xff09;&#xff1a; 在TCP/IP四层模型中的应用层进行加密。类似密码本&#xff0c;当前项目使用的是这种&#xff0c;可以看到所有响应内容&#xff0c;只是看不懂。 在TCP/IP四层模型中的运输层和应用层之间进行…

「Vue3面试系列」Vue3 所采用的 Composition Api 与 Vue2 使用的 Options Api 有什么不同?

文章目录 开始之前正文一、Options Api二、Composition Api三、对比逻辑组织Options APICompostion API 逻辑复用 小结 开始之前 Composition API 可以说是Vue3的最大特点&#xff0c;那么为什么要推出Composition Api&#xff0c;解决了什么问题&#xff1f; 通常使用Vue2开…