Linux的POSIX线程属性

时间:2021-03-07 21:54:46

创建POSIX线程的函数为

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

第1个参数为线程句柄(类似于文件描述符),第3个参数为线程启动函数(输入void*、返回void*,因为指向任何结构体/基本数据类型的指针都可以被看作void*,而void*一般都可以显式强制转换成指向对应类型的指针甚至整型,这是不支持纯C编程的常见技巧),第4个参数为传递给线程启动函数的参数。

而第2个参数一般都设置为NULL,此时采用默认的线程属性()。但是有需求的时候还是得去设置,即使是TLPI上也没有深入线程属性而只是在29.8线程属性一节给出了个例子。于是我通过结合手册以及查看pthread.h来深入了解其用法。

首先贴出TLPI上线程属性的设置示例(的完整版),该线程使用了分离(detach)属性,即线程创建后自动分离,而无需调用pthread_detach函数

// thread_attrib.c: 线程属性使用示例
// 编译方式: gcc thread_attrib.c -pthread
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h> // 检查错误码errnum, 若不为0则输出msg以及错误码相关信息
static inline void checkErrnum(int errnum, const char* msg)
{
if (errnum != 0) {
fprintf(stderr, "%s error: %s\n", msg, strerror(errnum));
exit(1);
}
} // 线程启动函数, 将输入看作字符串类型
static inline void* threadFunc(void* arg)
{
printf("%s\n", (char*) arg);
return NULL;
} int main()
{
int s; // 错误码 // 初始化线程属性
pthread_attr_t attr;
s = pthread_attr_init(&attr);
checkErrnum(s, "pthread_attr_init"); // 设置线程分离状态, 此属性保证线程
s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
checkErrnum(s, "pthread_attr_setdetachstate"); // 使用attr属性创建线程
pthread_t tid;
s = pthread_create(&tid, &attr, threadFunc, (void*) "Hello world!");
checkErrnum(s, "pthread_create"); // 销毁线程属性
s = pthread_attr_destroy(&attr);
checkErrnum(s, "pthread_attr_destroy"); sleep(1); // main线程休眠, 保证创建线程的threadFunc函数能够执行完毕
return 0;
}

需要注意的一点是:使用pthread_attr_init初始化线程属性,使用完(即传入pthread_create)后需要使用pthread_attr_destory销毁,从而释放相关资源。

同时也可以发现,线程属性的设置方式并不像那样简单,一般纯C中设置属性往往是通过亦或运算来设置不同类型。

比如系统调用open的第3个参数可以用S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH这样4个定义为整型的宏进行亦或运算来表示rw-r--r--的读/写/执行权限。

而线程属性的设置,对每个属性都有单独的函数,而每个函数都有各自的选项,比如这里的pthread_attr_setdetachstate,关键词是detachstate(分离状态)。第2个参数这里选择了PTHREAD_CREATE_DETACHED,代表线程创建后立刻分离

于是查看线程属性类型的定义

union pthread_attr_t
{
char __size[__SIZEOF_PTHREAD_ATTR_T];
long int __align;
};
#ifndef __have_pthread_attr_t
typedef union pthread_attr_t pthread_attr_t;
# define __have_pthread_attr_t
#endif

之前open的第3个参数相当于是0b0001、0b0010、0b0100这种类型的值求亦或,可以看做是位(bit)数组,数组的每一位只能是0或1,而每一个线程属性都可以有多个值,于是pthread_attr_t则是一个char数组,char的取值有256个,也就是说,每一个线程属性理论上可以支持最多256个可选值。数组大小_SIZEOF_PTHREA_ATTR_T则是由系统类型以及WORDSIZE来决定。

这里通过查看glib库的头文件pthread.h来一探究竟,看看线程属性有哪些。线程属性的设置函数的前缀都是pthread_attr_set,所以查找以此为前缀的函数即可。我将找到的函数列表整理如下

1、线程分离状态(Detach state),默认是JOINABLE,即其他线程可以通过pthread_join函数来取得该线程的返回结果(“连接”);DETACHED则是线程创建时即分离,无法被其他线程连接。

/* Detach state.  */
enum
{
PTHREAD_CREATE_JOINABLE,
#define PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_JOINABLE
PTHREAD_CREATE_DETACHED
#define PTHREAD_CREATE_DETACHED PTHREAD_CREATE_DETACHED
};
/* Get detach state attribute.  */
extern int pthread_attr_getdetachstate (const pthread_attr_t *__attr,
int *__detachstate)
__THROW __nonnull ((, )); /* Set detach state attribute. */
extern int pthread_attr_setdetachstate (pthread_attr_t *__attr,
int __detachstate)
__THROW __nonnull (());

2、线程栈末尾之后用以避免栈溢出的扩展内存的大小,若设置为0,则代表这种机制无效

/* Get the size of the guard area created for stack overflow protection.  */
extern int pthread_attr_getguardsize (const pthread_attr_t *__attr,
size_t *__guardsize)
__THROW __nonnull ((, )); /* Set the size of the guard area created for stack overflow protection. */
extern int pthread_attr_setguardsize (pthread_attr_t *__attr,
size_t __guardsize)
__THROW __nonnull (());

3、调度参数,类型为结构体struct sched_param,第1个参数为优先级,其他的参数未知。可移植的做法就是设置线程优先级。

/* The official definition.  */
struct sched_param {
int __sched_priority;
};
/* Return in *PARAM the scheduling parameters of *ATTR.  */
extern int pthread_attr_getschedparam (const pthread_attr_t *__restrict __attr,
struct sched_param *__restrict __param)
__THROW __nonnull ((, )); /* Set scheduling parameters (priority, etc) in *ATTR according to PARAM. */
extern int pthread_attr_setschedparam (pthread_attr_t *__restrict __attr,
const struct sched_param *__restrict
__param) __THROW __nonnull ((, ));

4、调度策略,SCHED_OTHER为默认值(分时调度策略),SCHED_FIFO和SCHED_RR均为实时调度策略,前者是先到先服务,后者是时间片轮转。

具体细节可以参考文章

Linux-进程调度算法

线程调度策略SCHED_RR(轮转法)和SCHED_FIFO(先进先出)之对比

/* Return in *POLICY the scheduling policy of *ATTR.  */
extern int pthread_attr_getschedpolicy (const pthread_attr_t *__restrict
__attr, int *__restrict __policy)
__THROW __nonnull ((, )); /* Set scheduling policy in *ATTR according to POLICY. */
extern int pthread_attr_setschedpolicy (pthread_attr_t *__attr, int __policy)
__THROW __nonnull (());
// bits/sched.h
/* Scheduling algorithms. */
#define SCHED_OTHER 0
#define SCHED_FIFO 1
#define SCHED_RR 2

5、调度器的继承模式,默认是INHERIT,继承自父线程的调度优先级,只有设置为EXPLICIT,自己“显式”设置的调度策略、优先级才会起作用。

/* Scheduler inheritance.  */
enum
{
PTHREAD_INHERIT_SCHED,
#define PTHREAD_INHERIT_SCHED PTHREAD_INHERIT_SCHED
PTHREAD_EXPLICIT_SCHED
#define PTHREAD_EXPLICIT_SCHED PTHREAD_EXPLICIT_SCHED
};
/* Return in *INHERIT the scheduling inheritance mode of *ATTR.  */
extern int pthread_attr_getinheritsched (const pthread_attr_t *__restrict
__attr, int *__restrict __inherit)
__THROW __nonnull ((, )); /* Set scheduling inheritance mode in *ATTR according to INHERIT. */
extern int pthread_attr_setinheritsched (pthread_attr_t *__attr,
int __inherit)
__THROW __nonnull (());

6、调度内容的作用域,SYSTEM代表该线程与系统的所有线程竞争资源,PROCESS代表该线程与进程中的其他线程竞争资源。

/* Scope handling.  */
enum
{
PTHREAD_SCOPE_SYSTEM,
#define PTHREAD_SCOPE_SYSTEM PTHREAD_SCOPE_SYSTEM
PTHREAD_SCOPE_PROCESS
#define PTHREAD_SCOPE_PROCESS PTHREAD_SCOPE_PROCESS
};
/* Return in *SCOPE the scheduling contention scope of *ATTR.  */
extern int pthread_attr_getscope (const pthread_attr_t *__restrict __attr,
int *__restrict __scope)
__THROW __nonnull ((, )); /* Set scheduling contention scope in *ATTR according to SCOPE. */
extern int pthread_attr_setscope (pthread_attr_t *__attr, int __scope)
__THROW __nonnull (());

7、线程的栈地址

/* Return the previously set address for the stack.  */
extern int pthread_attr_getstackaddr (const pthread_attr_t *__restrict
__attr, void **__restrict __stackaddr)
__THROW __nonnull ((, )) __attribute_deprecated__; /* Set the starting address of the stack of the thread to be created.
Depending on whether the stack grows up or down the value must either
be higher or lower than all the address in the memory block. The
minimal size of the block must be PTHREAD_STACK_MIN. */
extern int pthread_attr_setstackaddr (pthread_attr_t *__attr,
void *__stackaddr)
__THROW __nonnull (()) __attribute_deprecated__;

8、当前使用的最小栈地址的大小,当设置这个值时,栈的大小不能小于PTHREAD_STACK_MIN,不能大于系统的限制(用命令ulimit -s查看)

/* Return the currently used minimal stack size.  */
extern int pthread_attr_getstacksize (const pthread_attr_t *__restrict
__attr, size_t *__restrict __stacksize)
__THROW __nonnull ((, )); /* Add information about the minimum stack size needed for the thread
to be started. This size must never be less than PTHREAD_STACK_MIN
and must also not exceed the system limits. */
extern int pthread_attr_setstacksize (pthread_attr_t *__attr,
size_t __stacksize)
__THROW __nonnull (());

9、线程栈的地址和大小(需要定义宏__USE_XOPEN2K)

#ifdef __USE_XOPEN2K
/* Return the previously set address for the stack. */
extern int pthread_attr_getstack (const pthread_attr_t *__restrict __attr,
void **__restrict __stackaddr,
size_t *__restrict __stacksize)
__THROW __nonnull ((, , )); /* The following two interfaces are intended to replace the last two. They
require setting the address as well as the size since only setting the
address will make the implementation on some architectures impossible. */
extern int pthread_attr_setstack (pthread_attr_t *__attr, void *__stackaddr,
size_t __stacksize) __THROW __nonnull (());
#endif

10、线程的亲和性,(需要定义宏__USE_GNU)

参考文章:Linux下pthread的线程亲和性研究

#ifdef __USE_GNU
/* Thread created with attribute ATTR will be limited to run only on
the processors represented in CPUSET. */
extern int pthread_attr_setaffinity_np (pthread_attr_t *__attr,
size_t __cpusetsize,
const cpu_set_t *__cpuset)
__THROW __nonnull ((, )); /* Get bit set in CPUSET representing the processors threads created with
ATTR can run on. */
extern int pthread_attr_getaffinity_np (const pthread_attr_t *__attr,
size_t __cpusetsize,
cpu_set_t *__cpuset)
__THROW __nonnull ((, ));#endif