【Linux】学习记录_14_线程

news2024/9/21 2:37:18

14 线程

14.1 线程和进程

进程是资源管理的最小单位,每个进程都有数据段、代码段和堆栈段,进程切换时都有复杂的上下文切换等动作。进程切换上下文时, 需要重新映射虚拟地址空间、进出OS内核、寄存器切换,还会干扰处理器的缓存机制, 因此为了进一步减少CPU在进程切换时的额外开销,因此Linux进程演化出了另一个概念——线程。

线程是操作系统能够调度和执行的基本单位,在Linux中也被称之为轻量级进程。在Linux系统中, 一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等), 而将线程分配到某个cpu上执行。 一个进程可以拥有多个线程,它还可以同时使用多个cpu来执行各个线程 , 以达到最大程度的并行,提高工作的效率;同时,即使是在单cpu的机器上,也依然可以采用多线程模型来设计程序, 使设计更简洁、功能更完备,程序的执行效率也更高。

线程的本质是一个进程内部的一个控制序列,是进程里面的东西,一个进程可以拥有一个线程或者多个线程。

进程与线程

在进程中创建新线程时,新的执行线程将拥有自己的栈, 但与它的创建者共享全局变量、文件描述符、信号处理函数和当前目录状态。 也就是说,它只使用当前进程的资源,而不是产生当前进程的副本。

Linux系统中的每个进程都有独立的地址空间,一个进程崩溃后, 系统的保护模式下并不会对系统中其它进程产生影响,而线程只是一个进程内部的一个控制序列, 当进程崩溃后,线程也随之崩溃,所以一个多进程的程序要比多线程的程序健壮,但在进程切换时, 耗费资源较大,效率要差一些。但在某些场合下对于一些要求同时进行并且又要共享某些变量的并发操作, 只能用线程,不能用进程。

总的来说:

  • 一个程序至少有一个进程,一个进程至少有一个线程。
  • 线程使用的资源是进程的资源,进程崩溃线程也随之崩溃。
  • 线程的上下文切换,要比进程更加快速,本质上线程很多资源都是共享进程的,所以切换时, 需要保存和切换的项是很少的。

14.2 创建线程

可移植操作系统接口(Portable Operating System Interface,缩写为POSIX), POSIX是IEEE为要在各种UNIX操作系统上运行软件,而定义API接口的一系列互相关联的标准的总称, 其正式称呼为IEEEStd 1003,而国际标准名称为ISO/IEC9945,此标准源于一个大约开始于1985年的项目。 POSIX这个名称是由理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称。 它基本上是Portable Operating System Interface(可移植操作系统接口)的缩写, 而X则表明其对Unix API的传承。

应用程序使用POSIX标准的接口来调用系统函数, 应用程序将非常容易移植甚至直接兼容遵循POSIX标准的系统上

Linux系统下的多线程遵循POSIX标准,其中一套常用的线程库是pthread, 是一套通用的线程库,由 POSIX提出

#include <pthread.h>

链接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库, 所以在编译时要加上-lpthread 选项。

14.2.1 pthread_create()创建线程

pthread_create()函数是用于创建一个线程的,创建线程实际上就是确定调用该线程函数的入口点, 在线程创建后,就开始运行相关的线程函数。函数原型:

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

参数说明:

  • thread:指向线程标识符的指针。
  • attr:设置线程属性。
  • start_routine:start_routine是一个函数指针,指向要运行的线程入口,即线程运行时要执行的函数代码。
    • arg:运行线程时传入的参数。
  • 返回值:若线程创建成功,则返回0。若线程创建失败,则返回对应的错误代码。

14.3 线程属性

上面pthread_create中需要以线程属性作为输入参数,在Linux中线程属性结构如下:

typedef struct
{
    int                   etachstate;      //线程的分离状态
    int                   schedpolicy;     //线程调度策略
    structsched_param     schedparam;      //线程的调度参数
    int                   inheritsched;    //线程的继承性
    int                   scope;           //线程的作用域
    size_t                guardsize;       //线程栈末尾的警戒缓冲区大小
    int                   stackaddr_set;   //线程的栈设置
    void*                 stackaddr;       //线程栈的位置
    size_t                stacksize;       //线程栈的大小
}pthread_attr_t;

注意:pthread非Linux系统的默认库,而是POSIX线程库, 编译时需要加上-lpthread(或-pthread)显式指定链接该库。 函数在执行错误时的错误信息将作为返回值返回,不修改系统全局变量errno, 无法使用perror()打印

线程属性非常多,而且属性值不能直接设置,须使用相关函数进行操作,线程属性主要包括如下属性: 作用域(scope)、栈大小(stacksize)、栈地址(stackaddress)、优先级(priority)、 分离的状态(detachedstate)、调度策略和参数(scheduling policy and parameters)。 默认的属性为非绑定、非分离、1M的堆栈大小、与父进程同样级别的优先级。 下面简单讲解一下与线程属性相关的API接口:

API描述
pthread_attr_init()初始化一个线程对象的属性
pthread_attr_destroy()销毁一个线程属性对象
pthread_attr_getaffinity_np()获取线程间的CPU亲缘性
pthread_attr_setaffinity_np()设置线程的CPU亲缘性
pthread_attr_getdetachstate()获取线程分离状态属性
pthread_attr_setdetachstate()修改线程分离状态属性
pthread_attr_getguardsize()获取线程的栈保护区大小
pthread_attr_setguardsize()设置线程的栈保护区大小
pthread_attr_getscope()获取线程的作用域
pthread_attr_setscope()设置线程的作用域
pthread_attr_getstack()获取线程的堆栈信息(栈地址和栈大小)
pthread_attr_setstack()设置线程堆栈区
pthread_attr_getstacksize()获取线程堆栈大小
pthread_attr_setstacksize()设置线程堆栈大小
pthread_attr_getschedpolicy()获取线程的调度策略
pthread_attr_setschedpolicy()设置线程的调度策略
pthread_attr_setschedparam()获取线程的调度优先级
pthread_attr_getschedparam()设置线程的调度优先级
pthread_attr_getinheritsched()获取线程是否继承调度属性
pthread_attr_getinheritsched()设置线程是否继承调度属性

如果不是特别需要的话,是可以不需要考虑线程相关属性的,使用默认的属性即可。

14.3.1 初始化线程对象属性

使用pthread_attr_init()函数可以初始化线程对象的属性,函数原型:

int pthread_attr_init(pthread_attr_t *attr);
  • attr:指向一个线程属性的指针
  • 返回值:若函数调用成功返回0,否则返回对应的错误代码。
14.3.2 销毁一个线程属性对象

pthread_attr_destroy()用于销毁一个线程属性对象。函数原型:

int pthread_attr_destroy(pthread_attr_t *attr);
  • attr:指向一个线程属性的指针
  • 返回值:若函数调用成功返回0,否则返回对应的错误代码。
14.3.3 线程的分离状态

线程属性值中有一个分离状态,在任何一个时间点上,线程是可结合的(joinable), 或者是分离的(detached)。可结合的线程能被其他线程收回其资源和杀死;在被其他线程回收之前, 它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的, 它的存储器资源在它终止时由系统自动释放。

总而言之:线程的分离状态决定一个线程以什么样的方式来终止自己。

进程中的线程可以调用pthread_join()函数来等待某个线程的终止,获得该线程的终止状态,并收回所占的资源, 如果对线程的返回状态不感兴趣,可以将rval_ptr设置为NULL。

int pthread_join(pthread_t tid, void **rval_ptr)

除此之外线程也可以调用pthread_detach()函数将此线程设置为分离状态,设置为分离状态的线程在线程结束时, 操作系统会自动收回它所占的资源。设置为分离状态的线程,不能再调用pthread_join()等待其结束。

int pthread_detach(pthread_t tid)

如果一个线程是可结合的,线程在退出时不会自动释放自身资源,会成为僵尸线程, 该线程的退出值可以被其他线程获取。如果不需要某线程的退出值, 最好将线程设置为分离状态,以保证该线程不会成为僵尸线程。

如果在创建线程时就知道不需要了解线程的终止状态,那么可以通过修改pthread_attr_t结构中的detachstate属性, 让线程以分离状态启动,调用的pthread_attr_setdetachstate()函数原型如下:

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)

如果想要获取某个线程的分离状态,那么可以通过pthread_attr_getdetachstate()函数获取:

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

若函数调用成功返回0,否则返回对应的错误代码。

参数说明:

  • attr:指向一个线程属性的指针。
  • detachstate:如果值为PTHREAD_CREATE_DETACHED,则表示线程是分离状态, 如果值为PTHREAD_CREATE_JOINABLE则表示线程是结合状态。
14.3.4 线程的调度策略

线程属性里包含了调度策略配置,POSIX 标准指定了三种调度策略:

  • 分时调度策略,SCHED_OTHER。程属性的默认值,另外两种调度方式只能用于以超级用户权限运行的进程, 因为它们都具备实时调度的功能,但在行为上略有区别。
  • 实时调度策略,先进先出方式调度(SCHED_FIFO)。基于队列的调度程序,对于每个优先级都会使用不同的队列, 先进入队列的线程能优先得到运行,线程会一直占用CPU,直到有更高优先级任务到达或自己主动放弃CPU使用权。
  • 实时调度策略 ,时间片轮转方式调度(SCHED_RR)。与 FIFO相似,不同的是前者的每个线程都有一个执行时间配额, 当采用SHCED_RR策略的线程的时间片用完,系统将重新分配时间片, 并将该线程置于就绪队列尾,并且切换线程,放在队列尾保证了所有具有相同优先级的RR线程的调度公平。

与调度相关的API接口如下:

int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);

若函数调用成功返回0,否则返回对应的错误代码。

参数说明:

  • attr:指向一个线程属性的指针。
  • inheritsched:线程是否继承调度属性,可选值分别为
  • PTHREAD_INHERIT_SCHED:调度属性将继承于创建的线程,attr中设置的调度属性将被忽略。
  • PTHREAD_EXPLICIT_SCHED:调度属性将被设置为attr中指定的属性值。
  • policy:可选值为线程的三种调度策略,SCHED_OTHER、SCHED_FIFO、SCHED_RR。
14.3.5 线程的优先级

线程优先级就是这个线程得到运行的优先顺序,在Linux系统中,优先级数值越小, 线程优先级越高,Linux根据线程的优先级对线程进行调度,遵循线程属性中指定的调度策略。

获取、设置线程静态优先级(staticpriority)可以使用以下函数,注意,是静态优先级, 当线程的调度策略为SCHED_OTHER时,其静态优先级必须设置为0。该调度策略是Linux系统调度的默认策略, 处于0优先级别的这些线程按照动态优先级被调度,之所以被称为“动态”,是因为它会随着线程的运行, 根据线程的表现而发生改变,而动态优先级起始于线程的nice值,且每当一个线程已处于就绪态但被调度器调度无视时, 其动态优先级会自动增加一个单位,这样能保证这些线程竞争CPU的公平性。

线程的静态优先级之所以被称为“静态”,是因为只要你不强行使用相关函数修改它, 它是不会随着线程的执行而发生改变,静态优先级决定了实时线程的基本调度次序,它们是在实时调度策略中使用的。

int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);

参数说明:

  • attr:指向一个线程属性的指针。
  • param:静态优先级数值。

线程优先级有以下特点:

  • 新线程的优先级为默认为0。
  • 新线程不继承父线程调度优先级(PTHREAD_EXPLICIT_SCHED)
  • 当线程的调度策略为SCHED_OTHER时,不允许修改线程优先级,仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效, 并可以在运行时通过pthread_setschedparam()函数来改变,默认为0。
14.3.6 线程栈

线程栈是非常重要的资源,它可以存放函数形参、局部变量、线程切换现场寄存器等数据,线程使用是进程的内存空间,那么一个进程有n个线程,默认的线程栈大小是1M, 那么就有可能导致进程的内存空间是不够的,因此在有多线程的情况下,我们可以适当减小某些线程栈的大小, 防止进程的内存空间不足,而某些线程可能需要完成很大量的工作,或者线程调用的函数会分配很大的局部变量, 亦或是函数调用层次很深时,需要的栈空间可能会很大,那么也可以增大线程栈的大小。

设置、获取线程栈大小可以使用以下函数:

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);

参数说明:

  • attr:指向一个线程属性的指针。
  • stacksize:线程栈的大小。

14.4 线程退出

线程创建后,系统就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了, 这是线程的一种隐式退出的方法。 而另一种退出线程的方法是使用pthread_exit()函数,让线程显式退出,这是线程的主动行为。使用线程函数时,不能随意使用exit()退出函数来进行出错处理, 这是因为exit()函数的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后, 该进程中的所有线程都会被退出,因此在线程中只能调用线程退出函数pthread_exit()而不是调用进程退出函数exit()。

函数原型:

void pthread_exit(void *retval);

参数说明:

  • retval:如果retval不为空,将线程的退出值保存到retval中,如果不关心线程的退出值,形参为NULL即可。

一般情况下,进程中各个线程的运行是相互独立的,线程的终止并不会相互通知,也不会影响其他的线程, 终止的线程所占用的资源不会随着线程的终止而归还系统,而是仍为线程所在的进程持有, 这是因为一个进程中的多个线程是共享数据段的。从前面的文章我们知道进程之间可以使用wait()系统调用来等待其他进程结束一样, 线程也有类似的函数:

int pthread_join(pthread_t tid, void **rval_ptr)

如果某个线程想要等待另一个线程退出,并且获取它的退出值,那么就可以使用pthread_join()函数完成, 以阻塞的方式等待thread指定的线程结束,当函数返回时,被等待线程的资源将被收回,如果进程已经结束, 那么该函数会立即返回。并且thread指定的线程必须是可结合状态的,该函数执行成功返回0,否则返回对应的错误代码。

参数说明:

  • thread: 线程标识符,即线程ID,标识唯一线程。
  • retval: 用户定义的指针,用来存储被等待线程的返回值。

需要注意的是一个可结合状态的线程所占用的内存仅当有线程对其执行立pthread_join()后才会释放,因此为了避免内存泄漏, 所有线程的终止时,要么已被设为DETACHED,要么使用pthread_join()来回收资源。

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

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

相关文章

关于agi中的Function Calling深入解析

接口(Interface) 两种常见接口&#xff1a; 1、人机交互接口&#xff0c;User Interface,简称UI 2、应用程序编程接口&#xff0c;Application Programming Interface,简称API 接口能【通】的关键&#xff0c;是两边都要遵守约定。 人要按照UI的设计来操作。UI的设计要符合…

easyexcel升级3.3.4失败的经历

原本想通过easyexcel从2.2.6升级到3.3.3解决一部分问题&#xff0c;结果之前的可以用的代码&#xff0c;却无端的出现bug 1 Sheet index (1) is out of range (0…0) 什么都没有改&#xff0c;就出了问题&#xff0c;那么问题肯定出现在easyexcel版本自身.使用模板填充的方式进…

从构成看自来水厂自动化控制系统的创新与发展

自来水厂自动化控制系统涵盖了多个关键组成部分&#xff0c;包括水管理云平台、供水监控系统以及供水调度平台。 系统内嵌了一系列自主创新的核心算法&#xff0c;这些算法结合了数学建模、机器仿真和流体力学等多元数据模型&#xff0c;以优化设备间的关联和控制关系&#xf…

top实时系统监控工具-读书笔记(十一)

top 是 Linux 系统中一个非常实用的实时系统监控工具&#xff0c;它可以实时显示系统中各个进程的资源使用情况&#xff0c;包括 CPU 占用率、内存占用量、进程运行状态等信息。 命令格式 top 的基本格式如下&#xff1a; top [选项] 常用选项及其作用 -d 或 --delsay&…

Php-WebView 现代跨平台 GUI分享

GitHub :php-webview 一个用于 C/C 的小型跨平台 Web 视图库&#xff0c;用于构建现代跨平台 GUI。 该项目的目标是为最广泛使用的平台创建一个通用的 HTML5 UI 抽象层。 它支持双向 JavaScript 绑定&#xff08;从 C/C 调用 JavaScript 和从 JavaScript 调用 C/C&#xff09;。…

算法刷题-15

3.31 笔试真题2/5 皇后攻击位置 国际象棋比赛上&#xff0c;行数是数字1-8&#xff0c;列数是字母a到h&#xff0c;比如第二行第四列就是d2 现在在某位置放置一个皇后的话&#xff0c;皇后能攻击到的位置有哪些&#xff08;皇后攻击直线与斜线&#xff09; 输入&#xff1…

LMbench单独执行某一个组件 | benchmark教程

LMbench官网 > https://lmbench.sourceforge.net/man/ 下载源码&#xff0c;编译得到的Benchmark是由很多文件组成的&#xff0c;上面的链接中官方给出了每个组件的各参数的含义&#xff0c;可以对照着修改使用&#xff0c;以达到测试在某个组件上表现的作用。 以bw_mem为例…

【学习分享】通俗易懂!最早(晚)开始时间

【学习分享】通俗易懂&#xff01;最早&#xff08;晚&#xff09;开始时间 前言一、什么是最早&#xff08;晚&#xff09;开始时间&#xff1f;二、什么是关键路径&#xff1f;三、实例四、小结 前言 看了这么多篇求解最早开始时间和最晚开始时间的文章&#xff0c;结果一篇…

RTSP/Onvif视频监控平台EasyNVR如何提高匿名用户的用户名和密码安全性?

EasyNVR安防视频云平台是旭帆科技TSINGSEE青犀旗下支持RTSP/Onvif协议接入的安防监控流媒体视频云平台。平台具备视频实时监控直播、云端录像、云存储、录像检索与回看、告警等视频能力&#xff0c;能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、W…

vue3中所有页面需要手动刷新一下才能显示,控制台没有报错

1.问题 登录进来是进入首页&#xff0c;然后切换任何页面都是空白&#xff0c;但是控制台没有报错。在其他页面刷新后却能显示&#xff0c;然而切换到首页刷新后再切换到其他页面又是空白。 2.解决问题 原因&#xff1a;在于首页给了两个根标签&#xff0c;我把其中一个根标签…

解决VSCode中“#include错误,请更新includePath“问题

目录 1、问题原因 2、解决办法 1、问题原因 在编写C程序时&#xff0c;想引用头文件但是出现如下提示&#xff1a; &#xff08;1&#xff09;首先检查要引用的头文件是否存在&#xff0c;位于哪里。 &#xff08;2&#xff09;如果头文件存在&#xff0c;在编译时提醒VSCo…

ROM修改进阶教程------安卓7_____安卓13去除签名验证操作步骤解析

同类博文: 安卓玩机搞机技巧综合资源-----修改rom 制作rom 解包rom的一些问题解析【二十一】_qcn改区域锁-CSDN博客 安卓系列机型rom修改。如果你删减了系统相关的app。那么严重会导致开机系统卡米 定屏等问题。这类一般都是系统签名验证导致的。而破解签名验证一般都是修改…

JsonPath实战

概述 JsonPath&#xff0c;GitHub是一种简单的方法来提取给定JSON文档的部分内容&#xff0c;提供类似正则表达式的语法来解析JSON文档。 特性 入门 引入如下Maven依赖&#xff1a; <dependency><groupId>com.jayway.jsonpath</groupId><artifactId&…

Centos7虚拟机与真机乎ping以及虚拟机ping不通的原因

虚拟机网络完全正常的标准 物理机可以ping通虚拟机的IP虚拟机可以ping通物理机的IP虚拟机可以ping通baidu.com等网站 使用工具版本&#xff1a;Centos7 前提&#xff1a; 虚拟机必须开机才可以连接访问 克隆出来的虚拟机一定要手动修改IP&#xff0c;IP冲突的情况下不能联网 …

会员邀请:中华环保联合会征集会员单位、行业专家

为进一步发展壮大会员队伍&#xff0c;我们将充分吸收水环境治理领域具有先进理念、技术创新、表现突出的优秀企事业单位作为会员。依托院士、专家团队的技术力量为企业、园区、区域流域、地方政的水环境问题提供全过程一站式服务;为企业搭建全产业链布局的专业服务平台&#x…

常见的数据抽取工具对比

1.什么是ETL? ETL&#xff0c;是英文Extract-Transform-Load的缩写&#xff0c;用来描述将数据从来源端经过抽取&#xff08;extract&#xff09;、转换&#xff08;transform&#xff09;、加载&#xff08;load&#xff09;至目的端的过程&#xff0c;是数据仓库的生命线。 …

羊大师分析,夏季羊奶的适合人群有哪些?

羊大师分析&#xff0c;夏季羊奶的适合人群有哪些&#xff1f; 夏季羊奶的适合人群相当广泛&#xff0c;主要包括以下几类人群&#xff1a; 生长发育中的孩子&#xff1a;羊奶富含营养&#xff0c;特别是蛋白质和矿物质&#xff0c;对孩子的生长发育有积极的促进作用。 中老年…

6.2 整合MongoDB

6.2 整合MongoDB 1. MongoDB简介2. MongoDB安装2.1 下载2.2 配置MongoDB2.3 MongoDB的启动和关闭1. 启动MongoDB2. 关闭MogoDB 2.4 安全管理 ***************************************************************************** 1. MongoDB简介 MongoDB是一种面向文档的数据库管…

txt转epub,epub转txt在线转换,电子书格式转换器!

随着电子书阅读器的普及&#xff0c;EPUB格式已成为数字出版的标准格式之一。EPUB文件不仅具有跨平台的兼容性&#xff0c;还提供了丰富的排版和交互功能&#xff0c;使得读者在阅读过程中获得更好的体验。然而&#xff0c;许多用户手中的电子书资源可能仍是以TXT格式存在。 T…

C++之类和对象--赋值运算符重载和const成员函数

目录 1.赋值运算符重载 1.1运算符重载 1.2赋值运算符重载 1.3其它特性 2.const成员函数 3.取地址及const取地址操作符重载 hello&#xff0c;欢迎大家来到小恶魔频道&#xff0c;今天讲解的是C里面的赋值运算符重载以及const成员函数 1.赋值运算符重载 1.1运算符重载 运…