Linux线程详解

一、线程的概念

在引入线程之前,进程 作为资源分配的最小单位 (分配得到了CPU的时间、内存等),操作系统通过调度算法实现多进程并发执行,共用CPU,但由于创建或撤销进程时,系统都要为之分配或回收资源,限制了并发程度的提高。后来,为了减少进程间切换的开销,可把进程作为资源分配单位和调度单位两个属性分隔开,于是有了线程即作为程序执行的最小单位同一进程中的线程共享相同地址空间 ,甚至线程之间也共享大部分资源。采用多线程来实现多任务开发可以极大减小切换开销。

Linux中不区分进程、线程,而是通过pthread线程库 来实现线程,也叫轻量级进程LWP。由于线程是进程的一部分,多个线程共享进程的很多资源 ,包括:可执行的指令、静态数据、进程中打开的fd、当前工作目录、用户ID、用户组ID等,当然线程也有自己的私有资源,包括线程ID(TID)、堆栈、errno、优先级、执行状态和属性等

二、线程的创建

cpp 复制代码
int  pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg)

@param: thread  子线程指针       创建完自动给thread赋值TID
    	attr    线程属性指针     给NULL默认为结合属性,线程的资源回收得配合join
    	routine 线程函数名       参数、返回值都为void * 类型的函数
   		arg     线程函数的参数指针 (多参数时设置结构体变量,将变量地址给指针即可)
@return: 成功返回0失败返回错误码

注意事项:

  • gcc编译时要加上 -lpthread 链接线程库
  • 进程退出,线程也全部结束退出。线程创建需要时间,如果进程退出得快,线程很可能得不到执行
  • 线程里可用pthread_self(void)获得自身TID 打印时格式化字符为%lu
  • 线程的参数传递实参须(void*)强转,形参给void *arg解引用(取值)须先强转回原类型
    • 1.指针传递: 如 *(int *)arg
    • 2.值传递:如 int(arg) 但要保证数据长度正确 值传递时 实参也可以直接给值 即 值作为地址 传给形参
  • 查看线程命令 ps -eLf

三、线程的退出

仅仅结束退出线程,不涉及资源的释放和回收

cpp 复制代码
void pthread_exit(void *retval)    //return 也可以退出线程
    
@param: retval 一般用字符串  可被其他线程通过pthread_join获取

四、线程的回收

主动回收 结合属性线程 资源,阻塞等待指定线程退出

cpp 复制代码
int pthread_join(pthread_t thread, void **retval)
    
@param:retval 指针的地址!

成功后retval指向exit的退出状态并返回0,失败返回错误码

示例:

cpp 复制代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg){
    printf("This is child thread\n");
    sleep(25);
    pthread_exit("thread return");

}


int main(){
    pthread_t tid[100];
    void *retv;
    int i;
    for(i=0;i<100;i++){
        pthread_create(&tid[i],NULL,func,NULL);
    }
    for(i=0;i<100;i++){
        pthread_join(tid[i],&retv);
        printf("thread ret=%s\n",(char*)retv);
    }
    while(1){    
        sleep(1);
    } 

}

五、线程的分离

因为exit只能结束线程但不能释放资源 ,对于属性为结合的线程,还需要join来主动阻塞等待回收,十分不方便,可以令其和主线程分离线程退出后系统主动回收资源,所以不用也不能调用join函数再重复回收

有以下两种方法:

1.调用线程分离函数

cpp 复制代码
int pthread_detach(pthread_t thread)     
功能:指定线程分离 通常在子线程里调用,与主线程分离

2.创建线程时设置其属性为分离

cpp 复制代码
pthread_attr_t attr;  //定义一个线程属性结构体变量
pthread_attr_init(&attr); //初始化线程属性
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置线程属性为分离

pthread_t tid;
pthread_create(&tid,&attr,线程函数名,线程函数参数指针);

线程的内存可通过 top -p PID查看进程中的线程详细信息

六、线程的取消

杀死线程!

前提是线程中有取消点(有阻塞的系统调用如sleep、printf),如果没有,可以pthread_testcancel()添加取消点

cpp 复制代码
int pthread_cancel(pthread_t thread)

int pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL); 
//取消失能,后续想要恢复使能,DISABLE换成ENABLE

线程被杀死的时间取决于其他线程何时调用该函数,而能否真正杀死还要看线程里是否取消使能了,如果失能了直到重新使能才可以成功杀死,也就是说,cancel线程后,相当于给线程发送一个取消信号,当线程执行完取消点同时取消使能则被杀死,后续代码不再执行;如果子线程取消失能了,执行完取消点也不停下,取消信号未决,等到取消使能后马上被杀死

杀死线程,线程的资源也得不到回收

子线程也可以杀死自己

示例:

cpp 复制代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg){
    printf("This is child thread\n");
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL); //失能   
//    while(1)
    {
        sleep(5);
        pthread_testcancel();
    }
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); //使能
    printf("should not print\n");
    while(1){
        sleep(1);
    }


    pthread_exit("thread return");
}


int main(){
    pthread_t tid;
    void *retv;
    int i;
    pthread_create(&tid,NULL,func,NULL);
    sleep(1);
    pthread_cancel(tid);
    pthread_join(tid,&retv);
//    printf("thread ret=%s\n",(char*)retv);
    while(1){    
        sleep(1);
    } 

}

七、线程的清理

线程被取消通常都在exit之前,容易出现malloc的资源泄露未得到释放,此时可以在线程中调用清理函数,然后在清理函数中释放。

cpp 复制代码
void pthread_cleanup_push(清理函数名,参数指针)
void pthread_cleanup_pop(int execute) //参数尽量>0

两个清理函数必须成对出现,否则编译不成功,可以写多对,遵循栈先入后出的原则

清理函数的执行条件:线程被杀死、线程调用exit(return都不行)、pop参数>0

cpp 复制代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void cleanup(void *arg){
    printf("cleanup,arg=%s\n",(char*)arg);

}
void cleanup2(void* arg){

    printf("cleanup2,arg=%s\n",(char*)arg);
}

void *func(void *arg){
    printf("This is child thread\n");
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);//线程可在任意时间点取消
    pthread_cleanup_push(cleanup,"abcd");
    pthread_cleanup_push(cleanup2,"efgh");
    //while(1)
    {
        sleep(1);
        
    }
    return "1234";

    while(1){
        printf("sleep\n");
        sleep(1);
    }
    pthread_exit("thread return");
    pthread_cleanup_pop(1);
    pthread_cleanup_pop(1);
    sleep(10);
    pthread_exit("thread return");
}


int main(){
    pthread_t tid;
    void *retv;
    int i;
    pthread_create(&tid,NULL,func,NULL);
    sleep(1);
//    pthread_cancel(tid);
    pthread_join(tid,&retv);
    printf("thread ret=%s\n",(char*)retv);
    while(1){    
        sleep(1);
    } 

}
相关推荐
慕y2745 分钟前
Java学习第十五部分——MyBatis
java·学习·mybatis
IC 见路不走28 分钟前
LeetCode 第91题:解码方法
linux·运维·服务器
翻滚吧键盘41 分钟前
查看linux中steam游戏的兼容性
linux·运维·游戏
小能喵1 小时前
Kali Linux Wifi 伪造热点
linux·安全·kali·kali linux
碣石潇湘无限路1 小时前
【AI篇】当Transformer模型开始学习《孙子兵法》
人工智能·学习
汀沿河1 小时前
8.1 prefix Tunning与Prompt Tunning模型微调方法
linux·运维·服务器·人工智能
zly35001 小时前
centos7 ping127.0.0.1不通
linux·运维·服务器
kikikidult2 小时前
(2025.07)解决——ubuntu20.04系统开机黑屏,左上角光标闪烁
笔记·ubuntu
小哥山水之间2 小时前
基于dropbear实现嵌入式系统ssh服务端与客户端完整交互
linux
future14122 小时前
C#每日学习日记
java·学习·c#