当前位置:   article > 正文

49.Linux 线程 Thread_pthread_attr_destroy

pthread_attr_destroy

3.线程属性

        使用pthread_create()函数创建线程时,可以通过传入参数attr来设置线程的属性,该参数是一个pthread_attr_t类型的结构体,在调用pthread_create()之前应先初始化该结构体。pthread_attr_t结构体的定义如下:

该结构体中成员的值不能直接修改,必须使用函数进行相关操作。初始化线程属性结构体的函数为pthread_attr_init(),这个函数必须在pthread_create()之前调用,且线程终止后必须通过pthread_attr_destroy()函数销毁属性资源。

线程的属性非常多,而且其属性值不能直接设置,须使用相关函数进行操作,线程属性主要包括如下属性: 作用域(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()

设置线程是否继承调度属性

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

3.1. 初始化线程对象属性

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

int pthread_attr_init(pthread_attr_t *attr);
  • attr:指向一个线程属性的指针

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

3.2. 销毁一个线程属性对象

pthread_attr_destroy()函数用于销毁一个线程属性对象。 若pthread_create()函数使用了已经销毁的线程属性对象创建线程,会返回错误。

pthread_attr_destroy()函数原型:

int pthread_attr_destroy(pthread_attr_t *attr);
  • attr:指向一个线程属性的指针

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

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则表示线程是结合状态。

         pthread_attr_init()函数与pthread_attr_destroy()函数都存在于函数库pthread.h中,它们的声明分别如下:

int pthread_attr_init( pthread_attr_t *attr) ;

int pthread_attr_destory(pthread_attr_t *attr);

调用pthread_attr_init()后,线程属性attr会被设置为默认值。默认情况下线程处于非绑定、非分离状态,并与父进程共享优先级。若要使用默认状态,将pthrea_create()函数中的参数atrr设置为NULL即可;若要使用自定义属性创建线程,则需要使用Linux系统中提供的接口函数去修改程序中pthread_attr_t结构体变量各成员的值。这些接口函数都存在于函数库pthread.h中,下面将对线程属性结构体中的常用状态及其相关函数分别进行讲解。

       

 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则表示线程是结合状态。

1.线程的分离状态

线程的分离状态决定一个线程终止自身运行的方式,默认情况下线程处于非分离状态,Linux系统中可以通过pthread_attr_setdetachstate()函数修改线程属性中的分离状态。此外,Linux系统还提供了pthread_attr_getdetachstate()函数用于获取线程的分离状态。这两个函数的声明如下:

int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);

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

①参数attr表示线程属性 

②detachstate表示线程的分离状态属性。

调用set函数时,若将其参数detachstate设置为PTHREAD_CREATE_DETACHED,线程创建后将以分离状态启动。若函数调用成功则返回0,否则返回errno。

2.线程的调度策略

线程的调度策略决定了系统调用该线程的方法,Linux系统中调度策略分为三种:SCHED_OTHER、SCHED_FIFO、SCHED_RR

这三种调度策略的含义分别如下:

①SCHED_OTHER——分时调度策略。

②SCHED_FIFO——实时调度策略,先到先服务。

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

以上调度策略中

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

②实时调度策略通过实时优先级决定调度权值,若线程已准备就绪,除非有优先级相同或更高的线程正在运行,除非有优先级相同或更高的线程正在运行,否则该线程很快便会执行。 

而实时调度策略SCHED_FIFO与SCHED_RR的不同在于:

调度策略为SCHED_FIFO的线程一旦获取CPU便会一直运行,除非有优先级更高的任务就绪或主动放弃CPU;

调度策略为SCHED_RR的线程则会根据时间片轮询,若线程占用CPU的时间超过一个时间片,该线程就会失去CPU并被置于就绪队列队尾,确保与该线程优先级相同且调度策略SCHED_FIFO或SCHED_RR的线程能被公平调度。

Linux系统中用于设置和获取线程调度策略的函数分别为pthread_attr_setschedpolicy()和pthread_attr_getschedpolicy(),这两个函数的声明分别如下:

int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy);

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

以上两个函数中的参数attr表示线程属性,policy表示线程的调度策略,policy的默认值为SCHED_OTHER,调度策略SCHED_FIFO和SCHED_RR只对超级用户有效。若函数调用成功则返回0,否则返回errno。

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。

 

3.5. 线程的优先级

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

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

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

线程的调度参数是一个struct sched_param类型的结构体,该结构体中包含一个成员sched_priority,该成员是一个整型变量,代表线程的优先级。仅当调度策略为SCHED_FIFO或SCHED_RR时,成员sched_priority才有效。Linux系统中用于设置和获取调度参数的函数为pthread_attr_setschedparam()和pthread_attr_getschedparam(),函数声明分别如下:

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。

3.6线程的继承性

inherit   继承

explicit   显示

        线程的继承性决定线程调度策略属性和线程调度参数的来源,其来源有两个:一是从创建该线程的线程属性中继承,二是从线程属性结构体中获取。线程的继承性没有默认值,若要使用该属性,必须对其进行设置。Linux系统中用来设置和获取线程继承性的函数为pthread_attr_setinheritsched()和pthread_attr_getinheritsched(),这两个函数的声明分别如下:

int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);

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

①attr代表线程属性

②参数inheritsched代表线程的继承性,该参数的常用取值为PTHREAD_INHERIT_SCHED和PTHREAD_EXPLICIT_SCHED

①PTHREAD_INHERIT_SCHED表示使新线程继承其父线程中的调度策略和调度参数

②PTHREAD_EXPLICIT_SCHED表示使用在attr属性中显示设置的调度策略和调度参数

若函数调用成功则返回0,否则返回errno。

3.7  线程的作用域

scope   (作用域)范围

        线程的作用域控制线程获取资源的范围Linux系统中使用pthread_attr_setscope()函数pthread_attr_getscope()函数设置和获取线程的作用域,这两个函数的声明分别如下:

int pthread_attr_getscope(pthread_attr_t *attr,int scope);

int pthread_attr_getscope(pthread_attr_t *attr,int *scope);

①参数attr代表线程属性

②参数scope代表线程的作用域,该参数常用的取值为PTHREAD_SCOPE_PROCESSPTHREAD_SCOPE_SYSTEM,分别代表在进程中竞争资源在系统层级竞争资源

若函数调用成功则返回0,否则返回-1。

3.8. 线程栈

线程栈是非常重要的资源它可以存放函数形参、局部变量、线程切换现场寄存器等数据, 在前文我们也说过了,线程使用的是进程的内存空间,那么一个进程有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:线程栈的大小

  • Linux中也提供用于设置和获取栈地址、栈末尾警戒区大小的函数,它们的函数声明分别如下:

    int pthread_attr_setstackaddr(pthread_attr_t *attr,void *stackaddr);

    int pthread_attr_getstackaddr(pthread_attr_t *attr,void **stackaddr);

    int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);

    int pthread_attr_getguardsize(pthread_attr_t *attr,size_t guardsize);

    此外,Linux系统中还提供了pthread_attr_setstack()函数和pthread_attr_getstack()函数,这两个函数可以在一次调用中设置或获取线程属性中的栈地址与栈容量,它们的函数声明分别如下:

    int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr,size_t stacksize);

    int pthread_attr_getstack(pthread_attr_t *attr,void **stackaddr,size_t stacksize);

    以上两个函数中的参数attr、stackaddr、stacksize分别代表线程属性、栈空间地址、栈空间容量。若函数调用成功则返回0,否则返回errno。

    以上介绍的几组函数都用于在创建线程前对线程属性进行设置,下面通过一个案例来展示使用线程属性控制线程状态的方法。

    案例9-8:在程序中通过设置线程属性的方式设置线程分离状态和线程内部栈空间容量及栈地址,使程序不断创建线程,耗尽内存空间并打印线程编号。

    1. #include <stdio.h>
    2. #include <pthread.h>
    3. #include <string.h>
    4. #include <stdlib.h>
    5. #include <unistd.h>
    6. #define SIZE 0X90000000
    7. void *th_fun(void *arg)
    8. {
    9. while(1)
    10. sleep(1);
    11. }
    12. int main()
    13. {
    14. pthread_t tid; //线程id
    15. int err,detachstate;
    16. int i=1;
    17. pthread_attr_t attr; //线程属性
    18. size_t stacksize; //栈容量
    19. void *stackaddr; //栈地址
    20. pthread_attr_init(&attr); //初始化线程属性结构体
    21. //获取线程栈地址、栈容量
    22. pthread_attr_getstack(&attr,&stackaddr,&stacksize);
    23. //获取线程分离状态
    24. pthread_attr_getdetachstate(&attr,&detachstate);
    25. //判断线程分离状态
    26. if(detachstate==PTHREAD_CREATE_DETACHED)
    27. printf("thread detached\n");
    28. if(detachstate==PTHREAD_CREATE_JOINABLE)
    29. printf("thread join\n");
    30. else
    31. printf("thread unknown\n");
    32. //设置线程分离状态,使线程分离
    33. pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    34. while(1){
    35. //在堆上申请内存,指定线程栈的起始地址和大小
    36. stackaddr=malloc(SIZE);
    37. if(stackaddr==NULL){
    38. perror("malloc");
    39. exit(1);
    40. }
    41. stacksize=SIZE;
    42. //设置线程栈地址和栈容量
    43. pthread_attr_setstack(&attr,stackaddr,stacksize);
    44. //使用自定义属性创建线程
    45. err=pthread_create(&tid,&attr,th_fun,NULL);
    46. if(err!=0){
    47. printf("%s\n",strerror(err));
    48. exit(1);
    49. }
    50. i++;
    51. printf("%d\n",i); //打印线程编号
    52. }
    53. pthread_attr_destroy(&attr); //销毁attr资源
    54. return 0;
    55. }

    执行结果如下:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/181959
推荐阅读
相关标签
  

闽ICP备14008679号