线程(Pthread)

目录

多线程模式下cpu如何分配

这两种线程的优缺点

多个线程在进程中共享资源有哪些

非共享资源

[线程函数(NPTL API)](#线程函数(NPTL API))

线程分离态

线程退出方式

关于线程的能力

线程属性


线程是大多数操作系统支持的调度单位,执行单元,某些系统不支持线程技术。

一般情况下进程包含线程,线程比进程更轻量(体积更小,开销更小)。

进程是最小的分配资源单位,线程是最小的调度单位。

线程创建于进程中,于进程共享资源,完成特定任务,如果在意内存开销,使用多线程技术是一个很好的选择。

进程创建时,系统分配内存资源,执行单元(默认),线程访问进程内存。

线程就是寄存器和栈(线程可以占用时间片使用cpu,可以通过保存和恢复处理器现场避免寄存器冲突,所以线程是一个合格的调度单位)

多线程模式下cpu如何分配

进程的蜕化,如果进程中创建了新的线程,那么进程原本的执行单位成为主控线程,新创建的成为普通线程,便于区分和理解。

内核级线程:系统支持的线程,可以分配其内核对象与进程一样获取cpu。

普通线程:系统无法识别普通线程,无法将资源分发给普通线程。

普通线程虽然无法被系统直接分发资源,但是它可以使用cup,只要主线程释放交换给它即可。

混合型线程:可以为县城分配内核对象,得到更多的时间片,线程安装在用户层,较少线程调度开销。混合型线程支持的系统少。

这两种线程的优缺点

内核级:可以得到更多系统资源,缩短任务完成时间,所有的线程资源分配,访问和切换都要系统干预,开销较大。

用户级:用户线程是安装在进程用户空间的,所以完成线程切换或访问线程资源在用户空间即可完成,无需系统干预。

多个线程在进程中共享资源有哪些

1.全局变量共享。2.文件描述符表共享。3.PCB多线程共享。4.共享堆空间。5.信号处理行为共享。

非共享资源

1.线程栈(8M)2.TCB非共享3.线程的优先级指针非共享4.每个线程都有独立信号屏蔽字5.errno全局变量非共享(避免多线程使用errno异常,在线程使用erron为局部变量,但是线程有自己的错误处理方式,无需使用erron)

线程函数(NPTL API)

int err=pthread_create(pthread_t* tid,pthread_attr_t* attr,void*(*twk)(void*),void* arg)//创建线程函数,成功返回0,失败返回错误号。

tid=线程创建成功,将此线程的id传出到变量中。

attr=线程属性函数,传出NULL,表示使用默认属性。

twk=线程工作地址,此参数为函数指针。

arg=线程函数参数,系统创建线程后调用twk而后把arg传入twk中。

(线程数量计算:进程用户空间余量/线程栈大小=线程数量)

ps -eLf#可以查看系统中所有线程。ps -Lf pid#可以查看特定进程中的线程(pid进程id)

LWP轻量级进程编号(并不是tid,而是调度编号)NLWP轻量级进程数量

只要使用NPTL库函数,都要在编译使用链接库 -lpthread

pthread_t tid=pthread_self();//返回调用多线程的tid

看能创建多少线程:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
#include<stdio.h>
void* twk(void* arg)
{
    while(1)
        sleep(1);
}
int main()
{
    pthread_t tid;
    int err;
    int flag=0;
    while(1)
    {
        if((err=pthread_create(&tid,NULL,twk,NULL))>0)
        {
            printf("create failed %s\n",strerror(err));
            exit(0);
        }
        printf("%d\n",++flag);
    }
    return 0;
}

创建线程并打印id:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void* twk(void* arg)
{
    printf("my tid 0x%x\n",(unsigned int)pthread_self());
}
int main()
{
    pthread_t tid;
    int err;
    printf("master 0x%x\n",(unsigned int)pthread_self());
    if((err=pthread_create(&tid,NULL,twk,NULL))>0)
    {
        printf("create faill errno %s\n",strerror(err));
        exit(0);
    }
    sleep(1);
    printf("son tid 0x%x\n",(unsigned int)tid);
    return 0;
}

打印线程id习惯用16进制形态。

主线程创建成功后传出tid,与普通线程内部获取的tid值相等但是不等价,线程内部id可以保证当前线程有效性,但是其他线程中tid无法保证。

pthread_join(pthread_t tid,void** reval);//线程回收,接收线程返回值,如果不回收会引发僵尸线程(TCB)残留(阻塞函数,线程未退出等待,退出后立即回收)

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

void* twk(void* arg)
{
    printf("pthread tid=0x%x\n",(unsigned int)pthread_self());
    return (void*)8;
}
int main()
{
    pthread_t tid;
    void* reva;
    int err;
    if((err=pthread_create(&tid,NULL,twk,NULL))>0)
    {
        printf("create error %s\n",strerror(err));
        exit(0);
    }
    pthread_join(tid,&reva);
    printf("join success 0x%x,reva=%ld\n",(unsigned int)tid,(long int)reva);
    return 0;
}

pthread_exit((void *)9)//线程退出并返回特定值

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

void* twk(void* arg)
{
    printf("pthread tid=0x%x\n",(unsigned int)pthread_self());
    //return (void*)8;
    pthread_exit((void *)9);
}
int main()
{
    pthread_t tid;
    void* reva;
    int err;
    if((err=pthread_create(&tid,NULL,twk,NULL))>0)
    {
        printf("create error %s\n",strerror(err));
        exit(0);
    }
    pthread_join(tid,&reva);
    printf("join success 0x%x,reva=%ld\n",(unsigned int)tid,(long int)reva);
    return 0;
}

pthread_cancel(pthread_t tid);//线程取消,参数为目标的tid,可以将目标线程杀死。

哪怕进程中没有异常,不用调用系统函数,但是进程使用时间片,只要时间片耗尽产生中断就会处理信号,信号肯定功能杀死进程。

如果线程被取消,那么收到的返回值为-1,线程开发时不允许使用-1作为返回值,保留给cancel。

pthread_cancel就是看调没调用系统函数,调用了才能取消掉。

pthread_testcancel//执行一次系统调用,线程可以执行该函数检测是否有待处理的取消事件。

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void* twk(void* arg)
{
    while(1)
    {
        pthread_testcancel();
    }
    pthread_exit((void*)9);
}
int main()
{
    pthread_t tid;
    int err;
    void* reva;
    printf("Master tid=0x%x\n",(unsigned int)pthread_self());
    if((err=pthread_create(&tid,NULL,twk,NULL))>0)
    {

        printf("thread_create failed:%s\n",strerror(err));
        exit(0);

    }
    sleep(5);
    pthread_cancel(tid);
    if((err=pthread_join(tid,&reva))>0){

        printf("join failed:%s\n",strerror(err));
    }
    printf("master join success,tid 0x%x,reva %ld\n",(unsigned int)tid,(long int)reva);
    return 0;
}

线程分离态

线程结束的状态:1.PTHREAD_JOINABLE回收态线程,默认状态,线程结束后需要手动回收(pthread_join)2.PTHREAD_DETACHED分离态线程,结束后系统自动回收它的资源。

pthread_detach()可以将回收态设置为分离态。

pthread_detach(pthread_self());//将指定的线程设置分离。

线程只能有一种退出状态,两种状态互斥,分离设置不可逆转,无法变为回收态,只能从回收设置为分离。

对一个分离态进行回收操作(join)回收操作失败。

如果线程处于回收阶段(某个线程在等待回收他),那么对线程设置分离不会成功。

处于回收阶段:没sleep交出时间片,join快。

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void* twk(void* arg)
{
    pthread_detach(pthread_self());
    pthread_exit(NULL);
}
int main()
{
    pthread_t tid;
    int err;
    if((err=pthread_create(&tid,NULL,twk,NULL))>0)
    {
        printf("create err %s\n",strerror(err));
    }
    //sleep(5);
    if((err=pthread_join(tid,NULL))>0)
    {
        printf("join error %s\n",strerror(err));
    }
    else {
        printf("success\n");
    }
    return 0;
}

交出时间片,设置分离态成功了。

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void* twk(void* arg)
{
    pthread_detach(pthread_self());
    pthread_exit(NULL);
}
int main()
{
    pthread_t tid;
    int err;
    if((err=pthread_create(&tid,NULL,twk,NULL))>0)
    {
        printf("create err %s\n",strerror(err));
    }
    sleep(5);
    if((err=pthread_join(tid,NULL))>0)
    {
        printf("join error %s\n",strerror(err));
    }
    else {
        printf("success\n");
    }
    return 0;
}

线程退出方式

return 0;普通线程执行,线程返回,线程退出。主线程执行,进程退出,线程关闭。

pthread_exit(); 不论是主线程还是普通线程执行均为线程退出,不影响进程。

pthread_cancel();取消目标线程,与进程无关。

exit();无论主线程还是普通线程执行,进程退出,所有线程关闭,释放资源。

关于线程的能力

主线程创建的线程,也可以继续线程的创建,普通线程可以回收其他线程,也可以创建或取消其他线程。

线程属性

pthread_attr_t线程属性结构体:

1.线程优先级指针(一般为默认值,优先级越高,线程可以得到的资源越多)

2.线程警戒缓冲区(防止线程栈溢出)4096bytes如果溢出尝试访问越界缓冲区,直接抛出异常,SIGBUS。

3.线程退出状态(存储线程退出状态,默认情况下均为回收态)。

4.线程栈大小(线程栈大小,系统根据里面的值决定创建多大的线程栈)。

5.线程栈地址(存储线程栈地址但是默认为nil)。

使用自定义结构体可以实现哪些开发目的:1.直接创建分离态线程,而不是后期修改。2.通过修改线程栈的信息提高线程创建数量。

初始化结构体,初始化后的结构体为默认属性pthread_attr_init(&attr);

获取状态传出pthread_attr_getdetachstate(&attr,int* detachstate);

设置退出状态pthread_attr_setdetachstate(&attr,int detachstate);

PTHREAD_CREATE_DETACHED//分离关键字

PTHREAD_CREATE_JOINABLE//回收关键字

创建线程使用自定义结构体pthread_create(&tid,&attr,twk,NULL);

属性使用完毕释放pthread_attr_destroy(&attr);

查看默认退出状态:

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    pthread_t tid;
    int de;
    pthread_attr_t at;
    pthread_attr_init(&at);
    pthread_attr_getdetachstate(&at,&de);
    if(de==PTHREAD_CREATE_JOINABLE)
    {
        printf("join\n");
    }
    if(de==PTHREAD_CREATE_DETACHED)
    {
        printf("detach\n");
    }
    return 0;
}

设置分离态:

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
void* twk(void* arg)
{
    while(1)
        sleep(1);
}
int main()
{
    pthread_t tid;
    int de;
    pthread_attr_t at;
    pthread_attr_init(&at);
    pthread_attr_getdetachstate(&at,&de);
    pthread_attr_setdetachstate(&at,PTHREAD_CREATE_DETACHED);
    pthread_create(&tid,&at,twk,NULL);
    int err;
    if((err=pthread_join(tid,NULL))>0)
    {
        printf("join error %s\n",strerror(err));
    }
    else{
        printf("join success\n");
    }
    return 0;
}

pthread_detach();多线程模型中少数线程要设置分离,使用函数设置。如果大量使用分离线程可以通过属性的方式来创建。

如果用户需要自定义栈大小,用户需要自行申请空间size_t stacksize=0x100000,void* stackaddr=malloc(stacksize)

线程栈大小=stacksize,线程栈地址=stackaddr。

pthread_attr_getstack(&attr,void** stackaddr,size_t*stacksize);//获取属性中的栈信息

pthread_attr_setstack(&attr,void* stackaddr,size_t stacksize);//设置栈信息

改变线程栈大小增加线程数量(32位有效,64位基本无效)

cpp 复制代码
​
    void* stackaddr;
    size_t stacksize;
    pthread_attr_init(&at);
    pthread_attr_getstack(&at,&stackaddr,&stacksize);
    printf("stackaddr %p,stacksize %d\n",stackaddr,(int)stacksize);
    stacksize=0x100000;
    int err;
    while(1)
    {
        if((stackaddr=(void*)malloc(stacksize))==NULL)
        {
            printf("malloc error %s\n",strerror(err));
            exit(0);
        }
         pthread_attr_setstack(&at,stackaddr,stacksize);
        if((err=pthread_create(&tid,&at,twk,NULL))>0)
        {
            printf("create error %s\n",strerror(err));
            exit(0);
        }
         pthread_attr_destroy(&at);
    printf("%d\n",++f);
    }

​
相关推荐
Lw老王要学习29 分钟前
Linux容器篇、第一章_02Rocky9.5 系统下 Docker 的持久化操作与 Dockerfile 指令详解
linux·运维·docker·容器·云计算
橙子小哥的代码世界44 分钟前
【大模型RAG】Docker 一键部署 Milvus 完整攻略
linux·docker·大模型·milvus·向量数据库·rag
倔强的石头1062 小时前
【Linux指南】用户与系统基础操作
linux·运维·服务器
云上艺旅2 小时前
centos升级内核
linux·运维·centos
kaikaile19952 小时前
centos开启samba服务
linux·运维·centos
云上艺旅2 小时前
centos部署k8s v1.33版本
linux·云原生·kubernetes·centos
好多知识都想学2 小时前
Centos 7 服务器部署多网站
linux·服务器·centos
好多知识都想学2 小时前
centos 7 部署awstats 网站访问检测
linux·运维·centos
Li-Yongjun2 小时前
深度解析 Linux 内核参数 net.ipv4.tcp_rmem:优化网络性能的关键
linux·网络·tcp/ip
藥瓿亭3 小时前
K8S认证|CKS题库+答案| 10. Trivy 扫描镜像安全漏洞
linux·运维·服务器·云原生·容器·kubernetes·cks