线程的基本元素
线程的常见的基本操作
- 线程的创建
- 线程的终止
- 线程之间的同步
- 线程的调度
- 线程当中的数据管理
- 线程与进程之间的交互
进程与线程之间共享一些内核数据结构
- 已经打开的文件描述符
- 当前工作目录
- 用户id和用户组id
- 全局数据段的数据
- 进程的代码
- 信号以信号处理函数
线程所独有的
- 线程的ID
- 寄存器线程和栈空间
- 线程的栈当中的局部变量个返回地址
- 信号掩码
- 线程自身的优先级
- errno
线程属性
线程属性结构pthread_attr_t定义在pthread.h的头文件里。线程属性结构如下:
typedef struct
{
int detachstate; // 线程的分离状态
int schedpolicy; // 线程调度策略
struct sched_param schedparam; // 线程的调度参数
int inheritsched; // 线程的继承性
int scope; // 线程的作用域
size_t guardsize; // 线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void * stackaddr; // 线程栈的位置
size_t stacksize; // 线程栈的大小
}pthread_attr_t;
线程属性的初始化及去初始化
使用pthread_attr_init()函数会使用默认值初始化线程属性结构体attr,等同于调用线程初始化函数时将此参数设置为NULL,使用前需要定义一个pthread_attr_t属性对象,此函数必须在pthread_create()函数之前调用。
pthread_attr_destroy()函数对attr指向的属性去初始化,之后可以再次调用 pthread_attr_init()函数对此属性对象重新初始化。
函数原型
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy((pthread_attr_t *attr);
参数说明
attr:指向线程属性的指针
返回值
只返回0,总是成功
线程的分离状态
线程分离状态属性值state可以是PTHREAD_CREATE_JOINABL(非分离)和 PTHREAD_CREATE_DETACHED(分离)。
线程的分离状态决定一个线程以什么样的方式来回收自己运行结束后占用的资源。线程的分离状态有2种:joinable或者detached。当线程创建后,应该调用pthread_join()或者pthread_detach()回收线程结束运行后占用的资源。如果线程的分离状态为joinable其他线程可以调用pthread_join()函数等待该线程结束并获取线程返回值,然后回收线程占用的资源。分离状态为detached的线程不能被其他的线程所join,自己运行结束后,马上释放系统资源。
函数原型
设置线程的分离状态/获取线程的分离状态,默认情况下线程是非分离状态
int pthread_attr_setdetachstate(pthread_attr_t *attr,int state);
int pthread_attr_getdetachstate(ptrhead_attr_t const *attr,int *state);
参数说明
attr:指向线程属性的指针
state:线程分离状态
函数返回
只返回0值
线程的调度策略
用于设置和得到线程的调度策略
函数原型
int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy);
int pthread_attr_getschedpolicy(pthread_attr_t const *attr,int *policy);
参数说明
attr:线程属性变量
policy:调度策略
线程的调度参数
pthread_attr_setschedparam()函数设置线程的优先级。使用param对线程属性优先级赋值。
Linux环境下参数sched_param定义在struct_sched_param.h里,结构为:
/* Data structure to describe a process' schedulability. */
struct sched_param
{
int sched_priority; // 线程优先级
};
结构体sched_param的成员sched_priority控制线程的优先级值。
函数原型
int pthread_attr_setschedparam(pthread_attr_t *attr,
struct sched_param const *param);
int pthread_attr_getschedparam(pthread_attr_t const *attr,
struct sched_param *param);
参数说明
attr :指向线程属性的指针
param:指向调度参数的指针
线程的堆栈大小
设置/获取 线程的堆栈大小, pthread_attr_setstacksize()函数可以设置堆栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如ARM体系结构中需要向4字节地址对齐)。
函数原型
int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stack_size);
int pthread_attr_getstacksize(pthread_attr_t const *attr,size_t *stack_size);
参数说明
attr:指向线程属性的指针
stack_size:线程堆栈大小
返回值
返回0,总是成功
线程堆栈大小和地址
设置 / 获取 线程的堆栈地址和堆栈大小。
函数原型
int pthread_attr_setstack(pthread_attr_t *attr,
void *stack_base,
size_t stack_size);
int pthread_attr_getstack(pthread_attr_t *attr,
void **stack_base,
size_t *stack_size);
参数说明
attr:指向线程属性的指针
stack_size : 线程堆栈大小
stack_bass: 线程堆栈地址
返回值
返回0,总是成功
线程作用域相关函数
设置/获取线程的作用域。
函数原型
int pthread_attr_setscope(pthread_attr_t *attr,int scope);
int pthread_attr_getscope(pthread_attr_t const *attr);
返回值
pthread_attr_setscope :
scope为PTHREAD_SCOPE_SYSTEM返回0
scope 为 PTHREAD_SCOPE_PROCESS则返回EOPNOTSUPP
scope为其他值则返回EINVAL
pthread_attr_getscope:
返回PTHREAD_SCOPE_SYSTEM
线程堆栈地址相关函数
函数功能
设置/获取线程的堆栈地址
函数原型
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stack_addr);
int pthread_attr_getstackaddr(pthread_attr_t const *attr, void **stack_addr);
返回值
返回EOPNOTSUPP
线程警戒缓冲区相关函数
设置线程的警戒缓冲区/获取线程的警戒缓冲区
函数原型
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guard_size);
int pthread_attr_getguardsize(pthread_attr_t const *attr, size_t *guard_size);
返回值
返回EOPNOTSUPP
线程取消
取消是一种让一个线程可以结束其它线程运行的机制,一个线程可以对另一个线程发送一个取消请求。根据设置的不同,目标可能会置之不理,可能会立即结束也有很可能会将它推迟到下一个取消点才结束。
发送取消请求给thread线程。Thread线程是否会对取消请求做出回应以及什么时候做出回应依赖于线程取消的状态及类型。
发送取消请求
设置取消状态,由线程自己调用。取消使能的线程将会对取消请求做出反应,而取消没有使能的线程不会对取消请求做出反应。
函数原型
int pthread_cancel(pthread_t thread);
参数说明
thread:线程句柄
返回值
只返回0,总是成功
设置取消状态
函数原型
int pthread_setcancelstate(int state,int *oldstate);
参数说明
state:
PTHREAD_CANCEL_ENABLE:取消使能
PTHREAD_CANCEL_DISABLE:取消不使能(默认)
oldstate:
保存原来的取消状态
返回值
成功返回0, state非PTHREAD_CANCEL_ENABLE或者PTHREAD_CANCEL_DISABLE返回EINVAL。
设置取消类型
设置取消类型,由函数自己调用
函数原型
int pthread_setcanceltype(int type,int *oldtype);
参数说明
type:
PTHREAD_CANCEL_DEFFERED:线程收到取消请求后继续运行至下一个取消点再结束。(默认值)
PTHREAD_CANCEL_ASYNCHRONOUS:线程立即结束
oldtype:
保存原来的取消类型
返回值
成功返回0, type非PTHREAD_CANCEL_DEFFERED或者PTHREAD_CANCEL_ASYNCHRONOUS返回EINVAL。
设置取消点
在线程调用的地方创建一个取消点。主要由不包含取消点的线程调用,可以回应取消请求。如果在取消状态处于禁用状态下调用pthread_testcancel(),则该函数不起作用。
函数原型
void pthread_testcancel(void);
取消点
线程接收取消请求后会结束运行的地方, 根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()等会引起阻塞的系统调用都是取消点。
一次性初始化
有时候需要对一些posix变量只进行一次初始化,如线程键。如果我们进行多次初始化程序就会出现错误。再需要保证多线程安全的情况下,就会用到pthread_once,函数的本意就是只运行某个函数一次
函数原型
int pthread_once(pthread_once_t * once_control, void (*init_routine) (void));
参数说明
once_control:一般赋值为PTHREAD_ONCE,表示只运行一次
init_routine:待运行函数
获取线程的栈帧和PC值
在多线程程序中,每个线程拥有自己的栈帧和PC寄存器。在下面的程序中,我们可以得到程序在执行时候的三个寄存器rsp,rbp,rip的值,可以看到两个不同的线程的输出是不一致的。
程序实例:
/*获取线程的栈帧*/
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
u_int64_t rsp;
u_int64_t rbp;
u_int64_t rip;
void find_rip() {
asm volatile(
"movq 8(%%rbp), %0;"
:"=r"(rip)::
);
}
void* func(void* arg) {
printf("In func\n");
asm volatile( \
"movq %%rsp, %0;" \
"movq %%rbp, %1;" \
:"=m"(rsp), "=m"(rbp):: \
);
find_rip();
printf("stack frame: rsp = %p rbp = %p rip = %p\n", (void*)rsp, (void*)rbp, (void*) rip);
return NULL;
}
int main() {
printf("================\n");
printf("In main\n");
asm volatile( \
"movq %%rsp, %0;" \
"movq %%rbp, %1;" \
:"=m"(rsp), "=m"(rbp):: \
);
find_rip();
printf("stack frame: rsp = %p rbp = %p rip = %p\n", (void*)rsp, (void*)rbp, (void*) rip);
printf("================\n");
pthread_t t;
pthread_create(&t, NULL, func, NULL);
pthread_join(t, NULL);
return 0;
}
运行结果:
线程与信号
pthread库提供一些函数用于信号处理,在pthread库中通过pthread_kill向其他进程发送信号。
函数原型
void pthread_kill(pthread_t pid, int signal);
pthread多线程程序中所有线程共享信号处理函数,如果在一个线程中为某个信号绑定信号处理函数,当这个信号触发后,其他的线程都会跟随响应,如果在一个线程中修改信号处理函数,这个结果也会影响到其他线程。
程序实例:
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/syscall.h>
#define gettidv1() syscall(__NR_gettid)
void sig(int signo) {
char s[1024];
sprintf(s, "signo = %d tid = %d pthread tid = %ld\n", signo, (long int)gettidv1(), pthread_self());
write(STDOUT_FILENO, s, strlen(s));
}
void sig2(int signo) {
char* s = "thread-defined\n";
write(STDOUT_FILENO, s, strlen(s));
}
void* func(void* arg) {
signal(SIGSEGV, sig2);
printf("pthread tid = %ld\n", pthread_self());
for(;;);
return NULL;
}
void* func02(void* arg) {
printf("pthread tid = %ld\n", pthread_self());
for(;;);
return NULL;
}
int main() {
signal(SIGSEGV, sig);
pthread_t t;
pthread_create(&t, NULL, func, NULL);
sleep(1);
pthread_t t2;
pthread_create(&t2, NULL, func02, NULL);
sleep(1);
pthread_kill(t2, SIGSEGV);
sleep(2);
return 0;
}
运行结果:
参考文章:
https://www.bookstack.cn/read/rtthread-manual-doc/16.9.md
https://segmentfault.com/a/1190000042809162