Linux系统编程【线程属性】(2)

举报
xcc-2022 发表于 2022/10/24 20:54:53 2022/10/24
【摘要】 08. 线程属性(了解) 8.1 概述Linux下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。typedef struct{ int etachstate;...

08. 线程属性(了解)

8.1 概述

Linux下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。

如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。

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;

主要结构体成员:

  1. 线程分离状态

  2. 线程栈大小(默认平均分配)

  3. 线程栈警戒缓冲区大小(位于栈末尾)

  4. 线程栈最低地址

属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。

线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。

8.2 线程属性初始化和销毁

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);
功能:
    初始化线程属性函数,注意:应先初始化线程属性,再pthread_create创建线程
参数:
    attr:线程属性结构体
返回值:
    成功:0
    失败:错误号

int pthread_attr_destroy(pthread_attr_t *attr);
功能:
    销毁线程属性所占用的资源函数
参数:
    attr:线程属性结构体
返回值:
    成功:0
    失败:错误号

8.3 线程分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。

  • 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
  • 分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。

相关函数:

#include <pthread.h>

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:设置线程分离状态
参数:
    attr:已初始化的线程属性
    detachstate:    分离状态
        PTHREAD_CREATE_DETACHED(分离线程)
        PTHREAD_CREATE_JOINABLE(非分离线程)
返回值:
    成功:0
    失败:非0

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
功能:获取线程分离状态
参数:
    attr:已初始化的线程属性
    detachstate:    分离状态
        PTHREAD_CREATE_DETACHED(分离线程)
        PTHREAD _CREATE_JOINABLE(非分离线程)
返回值:
    成功:0
    失败:非0

这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。

要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。

设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

属性综合代码:

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

void *fun(void *argc)
{
    for(int i=0;i<5;i++)
    {
        printf("pthread do working %d...\n",i);
        sleep(1);
    }
    return NULL;
}
//测试分离
int main()
{
    pthread_t tid = -1;
    int ret = -1;
    pthread_attr_t attr; 

    //1.初始化线程属性
   ret = pthread_attr_init(&attr);
    if(ret != 0)
    {
        printf("pthread_attr_init failed...\n");
        return 1;
    }
    printf("线程属性初始化函数ok...\n");

    //2.设置线程为分离状态
   ret = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    if(ret != 0)
    {
        printf("pthread_attr_setdetachstate failed...\n");
        return 1;
    }
    //3.创建一个线程,使用初始化好的属性
    ret = pthread_create(&tid,&attr,fun,NULL);
    if(ret != 0)
    {
        printf("pthread_create failed...\n");
        return 1;
    }
    //4.销毁线程属性
    ret = pthread_attr_destroy(&attr);
    if(ret != 0)
    {
        printf("pthread_attr_destroy failed...\n");
        return 1;
    }
    //5.测试当前线程是否是分离状态
    ret = pthread_join(tid,NULL);
    if(ret != 0)
    {
        printf("当前线程为分离状态...\n");
        
    }
    else
    {
        printf("当前线程为非分离状态...\n");
    }

    while(1);

    return 0;
}

分析对比:

image-20220926223356219

8.4 线程栈地址

POSIX.1定义了两个常量来检测系统是否支持栈属性:

  • _POSIX_THREAD_ATTR_STACKADDR
  • _POSIX_THREAD_ATTR_STACKSIZE

也可以给sysconf函数传递来进行检测:

  • _SC_THREAD_ATTR_STACKADDR
  • _SC_THREAD_ATTR_STACKSIZE

当进程栈地址空间不够用时,指定新建线程使用由malloc分配的空间作为自己的栈空间。通过pthread_attr_setstack和pthread_attr_getstack两个函数分别设置和获取线程的栈地址。

#include <pthread.h>

int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr,  size_t stacksize);
功能:设置线程的栈地址
参数:
    attr:指向一个线程属性的指针
    stackaddr:内存首地址
    stacksize:返回线程的堆栈大小
返回值:
    成功:0
    失败:错误号

int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr,  size_t *stacksize);
功能:获取线程的栈地址
参数:
    attr:指向一个线程属性的指针
    stackaddr:返回获取的栈地址
    stacksize:返回获取的栈大小
返回值:
    成功:0
    失败:错误号

8.5 线程栈大小

当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。

#include <pthread.h>

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
功能:设置线程的栈大小
参数:
    attr:指向一个线程属性的指针
    stacksize:线程的堆栈大小
返回值:
    成功:0
    失败:错误号

int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
功能:获取线程的栈大小
参数: 
    attr:指向一个线程属性的指针
    stacksize:返回线程的堆栈大小
返回值:
    成功:0
    失败:错误号

8.6 综合参考程序

#define SIZE 0x100000

void *th_fun(void *arg)
{
    while (1)
    {
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    int err, detachstate, i = 1;

    pthread_attr_t attr;
    size_t stacksize;
    void *stackaddr;

    pthread_attr_init(&attr);  //线程属性初始化
    pthread_attr_getstack(&attr, &stackaddr, &stacksize); //获取线程的栈地址
    pthread_attr_getdetachstate(&attr, &detachstate);           //获取线程分离状态

    if (detachstate == PTHREAD_CREATE_DETACHED)
    {
        printf("thread detached\n");
    }
    else if (detachstate == PTHREAD_CREATE_JOINABLE)
    {
        printf("thread join\n");
    }
    else
    {
        printf("thread unknown\n");
    }
        
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置分离状态

    while (1) 
    {
        stackaddr = malloc(SIZE);
        if (stackaddr == NULL) 
        {
            perror("malloc");
            exit(1);
        }

        stacksize = SIZE;
        pthread_attr_setstack(&attr, stackaddr, stacksize); //设置线程的栈地址
        err = pthread_create(&tid, &attr, th_fun, NULL); //创建线程
        if (err != 0) 
        {
            printf("%s\n", strerror(err));
            exit(1);
        }
        printf("%d\n", i++);
    }

    pthread_attr_destroy(&attr); //销毁线程属性所占用的资源函数

    return 0;
}

8.7 线程使用注意事项

  1. 主线程退出其他线程不退出,主线程应调用pthread_exit

  2. 避免僵尸线程

  • a) pthread_join
  • b) pthread_detach
  • c) pthread_create指定分离属性

被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;

  1. malloc和mmap申请的内存可以被其他线程释放

  2. 应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit

  3. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制

09. 作业

1) 创建两个子线程, 第一个子线程输出所有的大写字母(A-Z),第二个子线程输出所有小写字母(a-z),每输出一个字母都要睡眠(usleep)。 100ms

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

//打印小写字母
void* fun2(void *arg)
{
    char ch =' ';
    for(ch = 'a'; ch<='z';ch++)
    {
        printf("%c",ch);
        usleep(10000);//100ms
    }
    return NULL;
}
//打印大写字母
void* fun1(void *arg)
{
    char ch =' ';
    for(ch = 'A'; ch<='Z';ch++)
    {
        printf("%c",ch);
        usleep(10000);//100ms
    }
    return NULL;
}
int main()
{

    int ret = -1;
    pthread_t tid1, tid2;

    //创建两个线程
    pthread_create(&tid1,NULL,fun1,NULL);
    pthread_create(&tid2,NULL,fun2,NULL);

    //等待两个线程结束
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    printf("\n main thread exit...\n");

    return 0;
}

xcc@ubuntu:~/8th$ ./a.out
AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz
main thread exit…
xcc@ubuntu:~/8th$

扩展

1.写一个以当前时间命名的文件

image-20220926154443206

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<time.h>

#define SIZE 32
int main()
{
    time_t t = -1;
    struct tm *pT = NULL;
    char  file_name[SIZE];
    //获取时间 秒
    t= time(NULL);
    if(t == -1)
    {
        perror("time");
        return 1;
    }
    printf("time:%ld\n",t);
   
    //获取当前时间
   // printf("ctime:%s",ctime(&t));
   // 转化为时间
   pT = localtime(&t);
   if(NULL == pT)
   {
       perror("localtime");
       return 1;
   }
   //转化为文件
    memset(file_name,0,SIZE);
    sprintf(file_name, "%s %d%d%d%d%d%d.log","touch",pT->tm_year+1900,pT->tm_mon+1,pT->tm_mday,pT->tm_hour,pT->tm_min,pT->tm_sec);

    printf("file_name:%s\n",file_name);

    system(file_name);

    return 0;
}

image-20220926161001101

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。