linux 线程详解

news2025/2/23 21:03:52

前言

        程序运行在内存空间中叫进程,进程中包含有若干线程,线程是系统调度和执行的基本单位。线程才是程序运行的实体,通常程序里的main()函数就相当于主线程,把进程理解成一个容器,里面可以包含有若干线程和若干资源(进程环境变量、打开的文件描述符、信号量、虚拟地址空间、代码、数据等),同一个进程中,所有线程共享进程所有资源,每个线程也有自己的程序计数器、栈空间和寄存器等。

1. 展望多进程、多线程的优劣

进程是资源分配的最小单位,而线程是程序运行的最小单位。

1.1 多进程

由于进程间是互不干扰的,每个进程都有自己的用户虚拟空间,可靠性高,但数据共享方面就会比较复杂,需要用到 IPC通信机制;

进程内存占用大,进程间的切换系统开销大、切换速度较慢;

它的编程与调试相对简单点。

1.2 多线程

同一进程下的多个线程通信容易,因为它们处于同一个虚拟空间下;

其中一个线程调用exit()或其它函数退出,会导致整个进程都退出,不太可靠;

线程占用内存少,线程间切换速度快,创建、销毁线程速度也比进程快;

编程与调试相对复杂。

2. 线程概述

进程有唯一的进程号PID,线程也不例外,也有唯一的ID号,线程ID不同于进程ID,它是只有在进程运行的时候才有意义。

linux系统中,线程ID是一个无符号长整形数,可以通过pthread_self(void)函数获取线程ID。

在shell终端编译链接的时候,由于pthread不在gcc默认的链接库中,需要在编译命令中使用 -l 选项指定链接库pthread,格式为:gcc -o app app.c -lpthread

2. 线程操作集API

2.1 创建线程

头文件:

#include <pthread.h>

函数原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

                                        void *(*start_routine) (void *), void *arg)

参数:

thread:一个传出参数,指向线程ID的指针;

attr:设置线程属性;通常设为NULL,表示使用线程默认属性;

start_routine:一个函数指针,指向线程运行的起始地址;

arg:传给线程执行函数的参数,通常设为NULL,表示不需要传参;

返回值:

成功:返回 0 ;

失败:返回错误编号

2.2 退出线程

在多线程的应用中,通常不会调用exit()等函数退出,这会造成整个进程一并退出,影响到其它还要运行的线程,所以通常是调用pthread_exit函数逐个退出线程。

头文件:

#include <pthread.h>

函数原型:

void pthread_exit(void *retval)

参数:

retval:表示线程的退出状态,通常设置为NULL

2.3 回收线程

调用pthread_join函数会阻塞等待线程的终止,并获取退出码。如果指定线程在调用pthread_join函数之前就已经退出了,则函数立马返回。pthread_join不能回收已处于death状态的线程。

头文件:

#include <pthread.h>

函数原型:

int pthread_join(pthread_t thread, void **retval)

参数:

thread:指定回收线程ID;

retval:线程退出返回值;不关心退出码则设为NULL;

返回值:

成功:返回 0 ;

失败:返回错误码

示例程序

一个进程中有三个线程,主线程创建完线程1和线程2后就退出了,线程1阻塞等待线程2的退出,线程2创建后延迟5s退出。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_t pth1,pth2;
void *ret = NULL;

void *pthread_1(void *arg)
{
    printf("this is pthread_1.pth-id:%ld\n",pthread_self());
    pthread_join(pth2,&ret);
    printf("pth2 has exit.ret:%d\n",ret);
    pthread_exit(NULL);
}

void *pthread_2(void *arg)
{
    printf("this is pthread_2.pth-id:%ld\n",pthread_self());
    sleep(5);
    pthread_exit((void *)5);
}

int main()
{
    if(pthread_create(&pth2,NULL,pthread_2,NULL) != 0){
        perror("pthread_2 create");
        exit(0);
    }

    if(pthread_create(&pth1,NULL,pthread_1,NULL) != 0){
        perror("pthread_1 create");
        exit(0);
    }
    printf("main pth-id:%ld\n",pthread_self());
    pthread_exit(0);

    return 0;
}

2.4 取消指定线程

调用pthread_cancel函数后,立马返回,不会等待线程退出后才返回。pthread_exit是主动退出,而pthread_cancel是被动退出的。

头文件:

#include <pthread.h>

函数原型:

int pthread_cancel(pthread_t thread)

参数:

thread:指定线程ID;

返回值:

成功:返回 0 ;

失败:返回错误码

示例程序:

一个进程中有三个线程,主线程创建完线程1和线程2后,延迟3s调用pthread_cancel函数被动退出线程2,线程1阻塞等待线程2的退出。(如果把线程2中while(1)里的sleep函数去掉,你会发现线程2不响应cancel信号,这是因为没有触发时间点,下面有解析“时间点”)。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_t pth1,pth2;
void *ret = NULL;

void *pthread_1(void *arg)
{
    printf("this is pthread_1.pth-id:%ld\n",pthread_self());
    pthread_join(pth2,&ret);
    printf("pth2 has exit.ret:%d\n",ret);
    pthread_exit(NULL);
}

void *pthread_2(void *arg)
{
    printf("this is pthread_2.pth-id:%ld\n",pthread_self());
    while(1)
    {
        printf("pth-2\n");
        sleep(1);
    }
}

int main()
{
    if(pthread_create(&pth2,NULL,pthread_2,NULL) != 0){
        perror("pthread_2 create");
        exit(0);
    }
    if(pthread_create(&pth1,NULL,pthread_1,NULL) != 0){
        perror("pthread_1 create");
        exit(0);
    }
    printf("main pth-id:%ld\n",pthread_self());
    sleep(3);
    pthread_cancel(pth2);
    pthread_exit(0);

    return 0;
}

2.4.1  线程取消状态

默认情况下,线程会响应pthread_cancel函数的调用,但也可以设置为不响应取消线程。

线程为不响应取消时,如果接收到取消请求,则会将请求挂起,直至线程的取消性状态变为响应。

头文件:

#include <pthread.h>

函数原型:

int pthread_setcancelstate(int state, int *oldstate)

参数:

state:设置新的响应状态;

        PTHREAD_CANCEL_ENABLE:线程响应取消;

        PTHREAD_CANCEL_DISABLE:线程不响应取消;

oldstate:保存旧的响应状态;NULL,表示不关心旧状态;

返回值:

成功:返回 0;

失败:返回错误码

2.4.2  线程取消类型

在线程状态设置为PTHREAD_CANCEL_ENABLE的情况下,调用pthread_setcanceltype函数设置线程响应 Cancel 信号的时间点,默认值为PTHREAD_CANCEL_DEFERRED。

说说什么叫时间点,可以理解从某函数的调用,如果没有达到时间点系统会认为还在执行重要指令,就不会响应取消线程,下图罗列一些触发时间点函数(也可以shell终端输入命令“man 7 pthreads”查看详细内容)。

头文件:

#include <pthread.h>

函数原型:

int pthread_setcanceltype(int type, int *oldtype)

参数:

type:设置线程类型;

 PTHREAD_CANCEL_DEFERRED:当线程执行到某个可作为取消点的函数时终止执行;

 PTHREAD_CANCEL_ASYNCHRONOUS:接收到 Cancel 信号后立即结束执行;

oldtype:保存线程旧的类型;设为NULL表示不关心旧类型;

返回值:

成功:返回 0;

失败:返回错误码

2.5 线程分离

调用pthread_join()获取其返回状态、回收线程资源,它会阻塞等待,就显得很不友好了;

调用pthread_detach函数将线程分离,当线程结束后,它的退出状态不由其它线程获取,而是由该线程自身自动释放。

头文件:

#include <pthread.h>

函数原型:

int pthread_detach(pthread_t thread)

参数:

thread:指定分离线程ID;

返回值:

成功:返回0;

失败:返回错误码

示例程序

线程2中调用分离线程函数将线程2分离出去了,线程1中调用回收线程函数,从打印信息可以看出,线程2还没有退出的时候,pthread_join函数就返回退出了。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_t pth1,pth2;
void *ret = NULL;

void *pthread_1(void *arg)
{
    printf("this is pthread_1.pth-id:%ld\n",pthread_self());
    pthread_join(pth2,&ret);
    printf("ret:%d\n",ret);
    pthread_exit(NULL);
}

void *pthread_2(void *arg)
{
    pthread_detach(pth2);
    printf("this is pthread_2.pth-id:%ld\n",pthread_self());
    sleep(5);
    printf("pth2 will exit.\n");
    pthread_exit((void *)5);
}

int main()
{
    if(pthread_create(&pth2,NULL,pthread_2,NULL) != 0){
        perror("pthread_2 create");
        exit(0);
    }
    sleep(1);
    if(pthread_create(&pth1,NULL,pthread_1,NULL) != 0){
        perror("pthread_1 create");
        exit(0);
    }
    printf("main pth-id:%ld\n",pthread_self());
    pthread_exit(NULL);
    return 0;
}

3. 线程的属性

线程创建函数pthread_create()的第三个参数可以设置线程的属性const pthread_attr_t *attr。

pthread_attr_t结构体内容如下:

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

3.1  初始化与销毁属性

该结构体成员变量的值不能随意更改,需要在线程创建前调用pthread_attr_init函数初始化;

线程退出时调用pthread_attr_destroy函数销毁属性资源。

头文件:

#include <pthread.h>

函数原型:

int pthread_attr_init(pthread_attr_t *attr)        //attr设为NULL表示默认配置属性;
int pthread_attr_destroy(pthread_attr_t *attr)

返回值:

成功:返回0;

失败:返回错误码

3.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(1):以分离状态启动线程,无法被其它线程回收;

      PTHREAD_CREATE_JOINABLE(0):默认值,非分离状态启动,可以被其它线程回收;

返回值:

成功:返回0;

失败:返回错误码

3.3 调度策略

分时调度策略通过nice值和counter值决定调度权值,nice值越小、counter越大,被调用的概率越高。

实时调度策略通过优先级决定调度权值;设置为SCHED_FIFO时,当前优先级最高者获得cpu运行,而同优先级下设置为SCHED_RR时,按照时间片轮询。

头文件:

#include <pthread.h>

函数原型:

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)        //设置调度策略;
int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy)      //获取调度策略;

 参数:

policy:

        SCHED_OTHER:分时调度策略;

        SCHED_FIFO,实时调度策略,先到先得;

        SCHED_RR,实时调度策略,按时间片轮询。

返回值:

成功:返回0;

失败:返回错误码

3.4 栈属性

线程的栈用于存储线程的私有数据。

头文件:

#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)

参数:

stackaddr:栈起始地址;

stacksize:栈大小;

返回值:

成功:返回0;

失败:返回错误码

测试例程

创建线程前初始化属性,设置分离状态为PTHREAD_CREATE_DETACHED(值为1),然后创建一个线程,并在线程里获取分离状态,打印出来。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_attr_t attr;

void *pthread_1(void *arg)
{
    int detachstate;

    printf("this is pthread_1.pth-id:%ld\n",pthread_self());
    pthread_attr_getdetachstate(&attr,&detachstate);
    printf("detachstate = %d\n",detachstate);
    pthread_exit(NULL);
}


int main()
{
    pthread_t pth1;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

    if(pthread_create(&pth1,NULL,pthread_1,NULL) != 0){
        perror("pthread_1 create");
        exit(0);
    }
    printf("main pth-id:%ld\n",pthread_self());
    pthread_exit(NULL);
    return 0;
}

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

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

相关文章

6)Mybatis启动流程

1. 首先Mybatis会加载配置文件mybatis-config.xml&#xff0c; 主要实现在Mybatis的builder模块&#xff0c;包路径org.apache.ibatis.builder&#xff0c;解析入口XMLConfigBuilder private void settingsElement(Properties props) {configuration.setAutoMappingBehavior(Au…

指针进阶篇(2)

进阶指针 &#x1f914;前言&#x1f914; 一、&#x1f60a;函数指针&#x1f60a; 二、&#x1f61c;函数指针数组&#x1f61c; 三 、&#x1f61d;指向函数指针数组的指针&#x1f61d; 四、&#x1f31d;回调函数&#x1f31d; &#x1f340;小结&#x1f340; &…

摩丝-题解

看到题目&#xff0c;怀疑是莫尔斯电码&#xff0c;打开发现果然是莫尔斯电码的点和划.. .-.. --- ...- . -.-- --- ..-简单说一下电报的原理最简单的电报模型就是一个电源&#xff0c;一个开关和一个电磁铁当需要长距离使用时候&#xff0c;需要用到继电器按下开关&#xff0c…

【BP靶场portswigger-服务端10】XML外部实体注入(XXE注入)-9个实验(全)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

C#【必备技能篇】使用NPOI实现对excel的读取和写入

文章目录1、Winform界面布局2、引用NPOI的dll3、源码4、运行效果5、NPOI的dll下载地址6、补充【以上步骤只能打开.xls文件&#xff08;97-2003版本&#xff09;&#xff0c;打不开.xlsx文件&#xff08;2007版本&#xff09;】1、Winform界面布局 2、引用NPOI的dll 3、源码 us…

(十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置

前言 本节内容我们主要介绍一下在jenkins中如何集成sonar qube代码质量检查工具&#xff0c;sonar qube可以在流水化项目集成部署前对我们的代码质量检查。开始本节内容前我们需要先搭建好sonar qube服务&#xff0c;关于sonar qube服务的搭建可参考作者往期博客内容&#xff…

P4391 [BOI2009]Radio Transmission 无线传输

题目描述 给你一个字符串 s_1s1​&#xff0c;它是由某个字符串 s_2s2​ 不断自我连接形成的。但是字符串 s_2s2​ 是不确定的&#xff0c;现在只想知道它的最短长度是多少。 输入格式 第一行一个整数 LL&#xff0c;表示给出字符串的长度。 第二行给出字符串 s_1s1​ 的一个子…

【linux入门】基础知识学习笔记

文章目录【第一章-宏观知识】1.硬件和软件的关系2.操作系统 是什么、作用是什么3.常见的操作系统4.Linux的诞生5.Linux内核 是什么6.Linux发行版 是什么7.WSL是什么8.虚拟机快照9.FinalShell&#xff08;Xshell替代品&#xff09;【第二章-Linux基础命令】1.Linux目录结构2.什么…

Linux---权限

目录 1.文件访问者的分类&#xff08;人/用户&#xff09; 2.文件类型和访问权限&#xff08;事物属性&#xff09; 3.文件权限值的表示方法 a)字符表示方法 b)8进制数值表示方法 4.文件访问权限的相关设置方法 4.1 改属性 4.2 改人&#xff08;改拥有者/所属组&#xff09;…

数组常用方法总结 (3) :map / forEach / every / some

map 遍历数组的每一项。如果是简单数组&#xff0c;不改变原始数组&#xff08;值类型&#xff09;。如果是对象数组&#xff0c;原始数组可以被改变&#xff08;引用类型&#xff09;。遍历原始数组&#xff0c;返回值为原始数组的每一项&#xff0c;最终可组合成新数组。 简…

LeetCode 78 子集 | 解题思路分享

原题链接&#xff1a;78. 子集 - 力扣&#xff08;LeetCode&#xff09; 题目难度&#xff1a;中等 题目描述 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任…

忆享聚焦|5G投资、网络安全市场、云计算、Web3技术……近期热点资讯一览

“忆享聚焦”栏目第11期来啦&#xff01;本栏目汇集近期互联网最新资讯&#xff0c;聚焦前沿科技&#xff0c;关注行业发展动态&#xff0c;筛选高质量讯息&#xff0c;拓宽用户视野&#xff0c;让您以最低的时间成本获取最有价值的行业资讯。目录行业资讯1. SA&#xff1a;全球…

ubuntu虚拟机VmWare与主机共享文件夹

一、说明&#xff1a; 宿主操作系统&#xff1a;Windows 11 64位。 客户操作系统&#xff1a;Ubuntu 18.04.1 64位。 虚拟机软件&#xff1a;VMware Workstation 17 pro 二、步骤&#xff1a; 1、参考教程链接1&#xff0c;在主机设置共享文件夹。 注意&#xff1a;教程链接…

聚焦技术,2022巨杉荣获国内外多家权威机构认可

作为分布式数据库的领先企业 巨杉成立十年来&#xff0c;一直聚焦分布式技术的自研与深耕 在分布式数据库领域已取得丰硕的成果 回望2022&#xff0c;巨杉除在客户案例及产品方面屡获殊荣外 也凭借过硬的技术实力及规模化的行业应用 得到多家国内外权威机构的认可 国际权威…

TensorRT学习笔记--基本概念和推理流程

目录 前言 1--Tensor RT基本概念 2--推理流程 3--实例代码 前言 以下 Tensor RT 的基本概念和推理流程均为博主自我的理解&#xff0c;可能部分内存会存在错误或偏差&#xff0c;仅供参考&#xff01; 1--Tensor RT基本概念 ① Logger&#xff1a;日志记录器&#xff0c;…

ssm:spring定时任务Task和CronExpression表达式

开发一个定时任务&#xff1a;每天晚上23点执行数据归集任务 首先Spring配置文件&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.or…

Java中的常用的代理模式

本文介绍在Java种常用的3种动态代理。 代理模式是23种模式中的一种&#xff0c;属于结构型设计模式。这种模式的作用就是要创建一个中间对象&#xff08;相当于中介或者代理对象&#xff09;&#xff0c;通过操作中间对象来间接调用目的对象的方法&#xff0c;字段等&#xff0…

Everything搜索知识总结

1.只知道那个文件以 .txt结尾 .*\.txt$ ($表示以什么结尾) 2.搜索某个路径下的文件 D:\ configure.bat (搜索D盘下的该文件,注意要用这种类型的"\",和被搜索的文件之间有空格;要先打出路径,再打出搜索文件.) 3.搜索指定路径下的多个文件 路径\ 文件1 | …

Halcon亚像素边缘缺陷检测案例

一、下面的案例是总结的Halcon边缘缺陷检测的一种情况。本案例是利用阈值分割获取金属区域&#xff0c;并利用boundary和edges_sub_pix获取到亚像素边缘。然后综合利用fit_rectangle2_contour_xld拟合出金属对应的放射矩形&#xff0c;最后利用dist_rectangle2_contour_points_…

【小白课程】openKylin用户手册原理解析,一招教你学会自定义!

openKylin用户手册是详细描述openKylin操作系统的功能和用户界面&#xff0c;让用户了解如何使用该软件的说明书。通过阅读openKylin用户手册&#xff0c;能够更快更好的上手和使用openKylin操作系统。今天就带大家简单了解下openKylin用户手册的实现原理以及如何自定义用户手册…