天天看点

89-线程属性

一直以来创建线程的时候,都将线程属性参数 pthread_attr_t 设置为 NULL. 通常来说这都能满足需求。本文将简要介绍 pthread_attr_t 中包含的属性。

1. pthread_attr_t

pthread_create 函数其中一个参数为此类型,表示将创建指定类型的线程,通常,该类型是一个结构体,包含下面这些属性:

  • Detach state: 线程的分离属性
  • Scope: 线程竞争域
  • Inherit scheduler: 调度继承性
  • Scheduling policy: 调度策略
  • Scheduling priority: 线程优先级
  • Guard size: 警界缓冲区大小,默认是一个 PAGE_SIZE(4096KB)
  • Stack address: 栈起始地址(栈最低内存地址)
  • Stack size: 栈大小

我只是把这些东西列出来而已,所以各位同学请不要拍砖,我知道大多数属性你是看不懂的。

实际上,这里大多数属性你最好不要去更改,我们能够使用的,一般就是线程的分离属性,对于其它的东西,只有在你理解了线程运行机制以及调度策略之后才能够看的懂。

不过,下面还是简单介绍一下不同属性要干什么事吧。

2. 不同属性的作用

2.1 Detach state

这个属性是线程的分离属性,它表示线程结束的时候,是否回收资源(比如它所使用的栈、线程结构体、线程过程函数的返回值等等)。

分离属性有两种情况:

  • ​PTHREAD_CREATE_JOINABLE​

    ​: 这种情况是默认的,表示结束的时候不回收资源,需要调用 pthread_join 函数显式回收,这种情况我们早已经学会了。
  • ​PTHREAD_CREATE_DETACHED​

    ​: 表示线程结束了,直接释放所占用的资源,这种情况下不可再使用 pthread_join 函数去 wait 它了。

2.2 Scope

线程竞争域,该属性有两种情况:

  • ​PTHREAD_SCOPE_SYSTEM​

    ​:表示线程和整个操作系统中的线程进行竞争,比如 CPU (在调度的时候,考虑系统中所有的线程).
  • ​PTHREAD_SCOPE_PROCESS​

    ​: 表示线程只和当前进程内的线程进行竞争,比如线程调度的时候,只考虑当前进程中的线程。

在 Linux 中,只支持​

​PTHREAD_SCOPE_SYSTEM​

​,因为 Linux 中实现的线程,实际上就是轻量级进程。

用于设置和获取 scope 值的函数如下:

int pthread_attr_setscope(pthread_attr_t *attr,int scope);

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

2.3 Inherit scheduler

调度继承性,该属性有两种情况:

  • ​PTHREAD_INHERIT_SCHED​

    ​: 继承进程的调度策略
  • ​PTHREAD_EXPLICIT_SCHED​

    ​: 不使用继承的调度策略,而是使用自己提供的调度策略。

用于设置和获取 inherit scheduler 值的函数如下:

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

int pthread_attr_setinheritsched(pthread_attr_t *attr,int      

2.4 Scheduling policy

调度策略,该属性有以下几个值:

  • ​SCHED_OTHER​

  • ​SCHED_BATCH​

  • ​SCHED_IDLE​

  • ​SCHED_FIFO​

  • ​SCHED_RR​

这些调度策略可分成两大类:

  • 普通调度策略(或者叫非实时调度策略),主要包括​

    ​SCHED_OTHER​

    ​​、​

    ​SCHED_BATCH​

    ​​和​

    ​SCHED_IDLE​

    ​ 这三种。
  • 实时调度策略,主要包括​

    ​SCHED_FIFO​

    ​​和​

    ​SCHED_RR​

    ​。

关于这些调度策略的含义,本文不详细讨论。

用于设置和获取 policy 值的函数如下:

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

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int      

2.5 Scheduling priority

调度优先级,通常该值通过下面的函数设定和获取。

pthread_attr_setschedparm(pthread_attr_t *attr, 
  const struct sched_param *param)

pthread_attr_getschedparm(pthread_attr_t *attr, 
  struct sched_param *param)      
struct sched_param {
  ...
  int sched_priority;
  ...      

对于普通调度策略来说,该值只能是 0. 对于动态优先级来说,该值的范围是 [1,99],但是 POSIX 规定,该范围需要通过 sched_get_priority_min 和 sched_get_priority_max 函数来获取。在具体实现中,最小是 32.

2.6 Guard size

警界缓冲区大小,默认是一个 PAGE_SIZE(4096KB). 用来设置和得到线程栈末尾的警戒缓冲区大小。如果线程栈运行到了警界区,就会收到信号。

可以使用下面两个函数来对其进行设置和获取:

int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize);

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);      

2.7 Stack address and Stack size

Stack address 表示栈起始地址(栈最低内存地址),一般来说在我们的 x86 或 x64 处理器上,栈都是往低地址的方向生长,所以该值一般表示栈的末尾(栈顶)。

Stack size 表示栈的大小。

可以使用下面两个函数来对其进行设置和获取:

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

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

对于栈的大小,还可以使用下面的函数:

int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);      

3. 实验

程序 threadattr 可以用来查看线程属性。该程序可以通过命令行传入一个参数,表示栈大小。另外,带参数的情况下,还会修改线程很多默认属性。

3.1 程序清单

// threadattr.c
#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define handle_error_en(en, msg) \
  do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

static void display_pthread_attr(pthread_attr_t *attr, char *prefix)
{
  int s, i;
  size_t v;
  void *stkaddr;
  struct sched_param sp;

  s = pthread_attr_getdetachstate(attr, &i);
  if (s != 0)
    handle_error_en(s, "pthread_attr_getdetachstate");
  printf("%sDetach state        = %s\n", prefix,
      (i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :
      (i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :
      "???");

  s = pthread_attr_getscope(attr, &i);
  if (s != 0)
    handle_error_en(s, "pthread_attr_getscope");
  printf("%sScope               = %s\n", prefix,
      (i == PTHREAD_SCOPE_SYSTEM)  ? "PTHREAD_SCOPE_SYSTEM" :
      (i == PTHREAD_SCOPE_PROCESS) ? "PTHREAD_SCOPE_PROCESS" :
      "???");

  s = pthread_attr_getinheritsched(attr, &i);
  if (s != 0)
    handle_error_en(s, "pthread_attr_getinheritsched");
  printf("%sInherit scheduler   = %s\n", prefix,
      (i == PTHREAD_INHERIT_SCHED)  ? "PTHREAD_INHERIT_SCHED" :
      (i == PTHREAD_EXPLICIT_SCHED) ? "PTHREAD_EXPLICIT_SCHED" :
      "???");

  s = pthread_attr_getschedpolicy(attr, &i);
  if (s != 0)
    handle_error_en(s, "pthread_attr_getschedpolicy");

  printf("%sScheduling policy   = %s\n", prefix,
             (i == SCHED_OTHER) ? "SCHED_OTHER" :
               (i == SCHED_FIFO)  ? "SCHED_FIFO" :
               (i == SCHED_RR)    ? "SCHED_RR" :
               "???");

  s = pthread_attr_getschedparam(attr, &sp);
  if (s != 0)
    handle_error_en(s, "pthread_attr_getschedparam");
  printf("%sScheduling priority = %d\n", prefix, sp.sched_priority);

  s = pthread_attr_getguardsize(attr, &v);
  if (s != 0)
    handle_error_en(s, "pthread_attr_getguardsize");
  printf("%sGuard size          = %d, prefix, v);

  s = pthread_attr_getstack(attr, &stkaddr, &v);
  if (s != 0)
    handle_error_en(s, "pthread_attr_getstack");
  printf("%sStack address       = %p\n", prefix, stkaddr);
  printf("%sStack size          = 0x%x, prefix, v);
}

static void * thread_start(void *arg)
{
  int s;
  pthread_attr_t gattr;

  /* pthread_getattr_np() is a non-standard GNU extension that
   *               retrieves the attributes of the thread specified in its
   *                             first argument */

  s = pthread_getattr_np(pthread_self(), &gattr);
  if (s != 0)
    handle_error_en(s, "pthread_getattr_np");

  printf("Thread attributes:\n");
  display_pthread_attr(&gattr, "\t");

  exit(EXIT_SUCCESS);         /* Terminate all threads */
}

int main(int argc, char *argv[])
{
  pthread_t thr;
  pthread_attr_t attr;
  pthread_attr_t *attrp;      /* NULL or &attr */
  int s;

  attrp = NULL;

  /* If a command-line argument was supplied, use it to set the
   * stack-size attribute and set a few other thread attributes,
   * and set attrp pointing to thread attributes object */

  if (argc > 1) {
    int stack_size;
    void *sp;

    attrp = &attr;

    s = pthread_attr_init(&attr);
    if (s != 0)
      handle_error_en(s, "pthread_attr_init");

    s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (s != 0)
      handle_error_en(s, "pthread_attr_setdetachstate");

    s = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
    if (s != 0)
      handle_error_en(s, "pthread_attr_setinheritsched");

    stack_size = strtoul(argv[1], NULL, 0);

    s = posix_memalign(&sp, sysconf(_SC_PAGESIZE), stack_size);
    if (s != 0)
      handle_error_en(s, "posix_memalign");

    printf("posix_memalign() allocated at %p\n", sp);

    s = pthread_attr_setstack(&attr, sp, stack_size);
    if (s != 0)
      handle_error_en(s, "pthread_attr_setstack");
  }

  s = pthread_create(&thr, attrp, &thread_start, NULL);
  if (s != 0)
    handle_error_en(s, "pthread_create");
  if (attrp != NULL) {
    s = pthread_attr_destroy(attrp);
    if (s != 0)
      handle_error_en(s, "pthread_attr_destroy");
  }

  pause();    /* Terminates when other thread calls exit() */      

3.2 编译和运行

  • 编译
$ gcc threadattr.c -o threadattr -lphtread      
  • 不带参数运行
$ ./threadattr      
89-线程属性

图1 不带参数运行

  • 带参数运行
$ ./threadattr 0x3000000      
89-线程属性

4. 总结

  • 知道线程属性包含哪些内容
  • 理解分离属性的作用

继续阅读