【Linux】线程Thread

🔥博客主页我要成为C++领域大神
🎥 系列专栏【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】

❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

线程概述

线程是进程中的一个执行单元

经典的并发模型,多进程模型,占用大量的内存开销,庞大的进程间调度开销,提供一种开销更小,更轻量级的并发解决方案,多线程技术。

线程在进程中,与进程共享资源和数据,进程是容器,线程是执行单元,进程退出可能导致所有线程退出。

每个进程中都有一个主控线程,还有其他的普通线程,操作系统会给进程分配需要的内存单元。

不支持线程技术的操作系统可以忽略线程概念,进程是调度单位。

多线程操作系统下,进程是内存管理单位,线程才是唯一的调度单元。

线程的CPU分配

Linux操作系统下,线程就是轻量级进程,每个进程分配一个LWP

在Linux操作系统下,所有的调度单位都是进程,淡化线程概念

线程的实现方式

用户线程(User Thread):在用户空间实现的线程,不是由内核管理的线程,是由用户态的线程库来完成线程的管理。可以利用第三方库的形式在不支持线程技术的系统下安装和使用线程,用户级线程的调度开销更小,因为大部分的操作都在用户层完成,无需内核干预

内核线程(Kernel Thread):操作系统中每创建一个线程,系统都会为其创建一个内核对象,此线程系统可以识别支持,会为线程执行资源(时间片)。Control控制器会为每个内核级线程创建内核对象,每个内核级线程都会由CPU进行时间片切换

轻量级进程(LightWeight Process):在内核中来支持用户线程;

进程的蜕化

进程内存资源独占,不与其他人共享,进程是独立的调度单位(无需考虑线程问题)

进程中出现了多个执行单元,讨论考虑线程问题

讨论和分析的蜕化,讨论线程,进程的讨论变为主线程和普通线程的分析和讨论

线程间的共享资源和非共享资源

PCB:共享资源

栈:线程栈空间非共享,每个线程创建后,会分配8MB线程栈

库:库资源共享

堆:共享堆空间

全局资源和静态资源:全局资源共享

代码段:代码段共享

文件描述符表:文件描述符表共享

信号的处理行为(某个线程改变信号行为)对所有线程共享。信号屏蔽字非共享,普通线程拥有独立的屏蔽字,拷贝继承于主线程

TCB:每个线程拥有自己的线程控制块,独立的tid

线程开发相关API接口

NPTL线程库

NPTL线程库是典型的内核级线程库,创建的线程可以被系统标识分配cpu资源(轻量级进程)

native Posix thread library

相关命令

ps aux 进程查看

ps ajx 进程关系查看

ps -eLf 所有线程查看 PID相同的是一组线程

LWPPID 相同的情况表示该进程只有一个线程。

ps -Lf pid 查看某一个进程下的线程

每个线程都会被系统分配lwp 调度编号, 便于系统管理调度线程,但是线程拥有独立的tid,线程id

头文件和函数

#include <pthread.h>使用线程函数的头文件

pthread_t tid线程tid类型

pthread_create

创建线程并指定类型pthread_create(pthread_t *tid,NULL,void * (thread_job)(void *),void *arg)第一个参数是线程id,第二个是线程属性,第三个参数是线程工作地址,第四个参数是线程工作参数。

返回值是err

下面是使用pthread_create创建线程的简单demo程序,通过PIDLWP相同可以看出,这个线程就是主控线程

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>


void * thread_job(void *arg)//普通线程的创建
{
    printf("普通线程被创建,参数为%d\n",*(int*)arg);
    return NULL;
}

int main()
{
    pthread_t tid;
    int code=1024;
    //普通线程都是由主控线程创建的
    pthread_create(&tid,NULL,thread_job,(void*)&code);
        printf("普通线程被主控线程创建出来\n");
    while(1) sleep(1);
    return 0;
}

在编译时需要链接库

gcc Pthrad_Create.c -lpthread -o app

关于线程函数的错误处理

线程函数出错,会返回错误号(err>0),使用char *errstr=strerror(err)进行错误判断

下面是打印错误日志的demo程序:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>


void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程被创建,参数为%d\n",*(int*)arg);
    while(1) sleep(1);
    return NULL;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    //普通线程都是由主控线程创建的
    while(1)
    {
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
    else
    {
        printf("线程%d成功创建\n",++flags);
    }
    }

    return 0;
}

32位Ubuntu操作系统下,一个进程只能创建381个线程

pthread_self

pthread_tid =pthread_self(void) 成功返回当前线程的id

主线程通过pthread_create创建普通线程,成功传出tid与普通线程自身利用pthread_self()得到的tid值相等但是进程状态不相等,因为pthread_self()获取tid时可以保证当前的有效性,主线程在获取tid的时候,普通线程可能已经退出。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>


void * thread_job(void *arg)//普通线程的创建
{
    printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    return NULL;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        while(1) sleep(1);//主控线程还在运行,普通线程已经退出
    return 0;
}

pthread_join(pthread_t tid,void **retval)

线程回收函数,可以回收线程资源的同时获取线程的返回值。经典的阻塞回收函数,会一致等待普通线程结束后,进行回收。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>


void * thread_job(void *arg)//普通线程的创建
{
    printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    return (void*)126;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        pthread_join(tid,&revtal);
        printf("Thread Exit Code:%ld\n",(long int)revtal);
    return 0;
}

可以看到,如果线程工作函数可以正常退出,退出码就是线程工作函数的返回值

pthread_cancel(pthread_t tid)

指定线程tid,取消结束线程

下面是简单的取消一个线程的demo程序:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>

void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    while(1)
    {
        printf("进程正在工作。。。\n");
        sleep(1);
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        sleep(5);
        pthread_cancel(tid);
        pause();
    
    return 0;
}

线程被成功结束,这个进程下只有一个主控线程在工作

当我们对代码稍作修改:

删除线程工作函数中的调用系统函数:

cpp 复制代码
void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    while(1)
    {
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        sleep(5);
        pthread_cancel(tid);
        pause();      
    return 0;
}

结果显示,pthread_cancel并没有成功取消线程。

这有些类似于信号,信号处理的三个切换条件:系统调用,软件中断,软件异常。而pthread_cancel取消事件的处理条件,必须有系统调用。

有一个专门为pthread_cancel提供系统调用的空函数:void pthread_testcancel(void),提供一次空的调用。

在线程工作函数加入这个函数再次尝试:

cpp 复制代码
void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    while(1)
    {
        pthread_testcancel(void);
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        sleep(5);
        pthread_cancel(tid);
        pause();      
    return 0;
}

可以看到只要一个主控线程,普通线程被结束了。

当我们对线程进行取消后,回收一下资源打印退出码看一下:

cpp 复制代码
void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    while(1)
    {
        pthread_testcancel(void);
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        sleep(5);
        pthread_cancel(tid);
        pthread_join(tid,&revtal);
        printf("Thread Exit Code:%ld\n",(long int)revtal);
        pause();      
    return 0;
}

可以看到退出码是-1,所以线程指定退出码时不允许使用-1,保留给pthread_cancel

pthread_detach(pthread_t tid)

用于将一个线程设置为分离态线程。

可以通过线程属性,批量创建分离态线程,这些线程诞生即是分离态。

线程有回收态和分离态,这两种状态是互斥的。

若修改线程退出状态,从回收态改为分离态,此操作不可逆,不能将分离线程变为回收态

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>

void * thread_job(void *arg)//普通线程的创建
{
    pthread_detach(pthread_self());
    while(1)
    {
        pthread_testcancel();
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        if((err=pthread_join(tid,&revtal))>0)
        {
            printf("join call failed %s\n",strerror(err));
        }
        printf("Thread Exit Code:%ld\n",(long int)revtal);
        pause();
        return 0;
}

joindetach会竞争时间片,所以有时线程以detach分离态被创建,当线程处于分离态时,无法手动回收,join执行失败

如果对一个分离态线程进行回收操作,pthread_join会失效返回。对一个已经处于回收阶段(join已经开始阻塞等待了)的线程设置分离,分离设置失败。

一个自行回收,一个由系统回收,一个可以得到线程退出码,一个无法获取

pthread_exit(void * exitcode)

线程退出函数,结束当前线程,与进程无关,退出码可以被join获取

线程的退出方式

return 0

主线程结束进程;结束普通线程,返回结果

pthread_cancel

主线程结束,与进程无关

普通进程结束,与进程无关

使用普通线程对主线程进行回收,产生了僵尸级线程

pthread_exit()

主线程结束,与进程无关

普通进程结束,与进程无关

exit()

进程退出,释放所有线程

线程属性

在使用创建线程函数时指定线程类型pthread_create(pthread_t **tid,pthread_attr_t attr,void * (thread_job)(void *),void *arg)

用第二个参数指定线程属性

pthread_attr_t线程属性类型,是一个结构体类型

修改线程程属性的操作

使用pthread_attr_getdetachstate查看线程默认退出属性

使用pthread_attr_getstack查看栈属性

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <signal.h>
#include <pthread.h>


int main()
{
    int State;
    void *stack_addr;//栈空间地址
    size_t stacksize;//栈空间默认大小
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_getdetachstate(&attr,&State);
        if(State==PTHREAD_CREATE_JOINABLE)
        printf("Thread Default State is Join\n");
    else
        printf("Thread Default State is Detach\n");
    pthread_attr_getstack(&attr,&stack_addr,&stacksize);
    printf("线程栈地址为%p 栈大小为%d\n",stack_addr,stacksize);
    return 0;
}

使用pthread_attr_setdetachstate修改线程默认退出状态为分离态

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <signal.h>
#include <pthread.h>

void *thread_job(void * arg)
{

}

int main()
{
    int State;
    void *stack_addr;//栈空间地址
    size_t stacksize;//栈空间默认大小
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_getdetachstate(&attr,&State);
        if(State==PTHREAD_CREATE_JOINABLE)
        printf("Thread Default State is Join\n");
    else
        printf("Thread Default State is Detach\n");
    pthread_attr_getstack(&attr,&stack_addr,&stacksize);
    printf("线程栈地址为%p 栈大小为%d\n",stack_addr,stacksize);

    pthread_t tid;

    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    pthread_create(&tid,&attr,thread_job,NULL);
    int err=pthread_join(tid,NULL);
    if(err>0)
        printf("Join Call Failed:%s\n",strerror(err));
    return 0;
}

使用pthread_attr_setstacksize修改默认线程栈大小

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <signal.h>
#include <pthread.h>

void *thread_job(void * arg)
{

}

int main()
{
    int State;
    void *stack_addr;//栈空间地址
    size_t stacksize;//栈空间默认大小
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_getdetachstate(&attr,&State);
        if(State==PTHREAD_CREATE_JOINABLE)
        printf("Thread Default State is Join\n");
    else
        printf("Thread Default State is Detach\n");
    pthread_attr_getstack(&attr,&stack_addr,&stacksize);
    printf("线程栈地址为%p 栈大小为%d\n",stack_addr,stacksize);

    pthread_t tid;

    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    stacksize=0x100000;
    int err;
    int flags=0;
    while(1)
    {
        if((stack_addr=(void*)malloc(stacksize))==NULL)
        {
            printf("malloc Stack Failed\n");
        }
        pthread_attr_setstacksize(&attr,stacksize);
        if((err=pthread_create(&tid,&attr,thread_job,NULL))>0)
        {
            printf("Create failed:%s\n",strerror(err));
            exit(0);
        }
        printf("Thread %d 创建成功\n",++flags);
    }
    return 0;
}

由结果可知,当将默认大小变小后,32位系统下可以创建的线程数量增加了。

相关推荐
HPC_fac130520678161 小时前
以科学计算为切入点:剖析英伟达服务器过热难题
服务器·人工智能·深度学习·机器学习·计算机视觉·数据挖掘·gpu算力
梓仁沐白2 小时前
ubuntu+windows双系统切换后蓝牙设备无法连接
windows·ubuntu
yaoxin5211232 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin2 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
Theodore_10223 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
网易独家音乐人Mike Zhou3 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
----云烟----5 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024065 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it5 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎