@ltlovezh
2020-02-15T17:50:50.000000Z
字数 6161
阅读 1204
C++
pthread
虽然C++ 11增加了标准线程库:std::thread
。但是在一些不需要跨平台的场景,或者一些历史库中,依然大量使用着pthread
库,所以两者都要学,两者都要硬。
Posix Thread是操作系统级的API规范,用来定义线程及线程间同步操作,采用C语言定义,主要在unix like系统上实现。
首先看一个最简单的多线程例子:
void *func(void *t) {
// 获取创建线程传递的参数
int arg = *((int *) t);
cout << "Sleeping in thread " << endl;
sleep(1);
cout << "Thread with arg: " << arg << endl;
}
int main(){
int arg = 10;
pthread_t thread;
int result = pthread_create(&thread, nullptr, func, (void *) &arg);
if (result) {
cout << "unable to create thread: " << result << endl;
exit(-1);
}
// 阻塞主线程,等待子线程执行结束
pthread_join(thread, nullptr);
cout << "Main: program exiting." << endl;
return 0;
}
主线程首先通过pthread_create
创建子线程,然后通过pthread_join
等待子线程运行结束。如果没有pthread_join
,那么程序会立即结束,程序结束后,所有子线程都会被终止(子线程的线程函数可能还没执行完)。
pthread_create的函数原型如下所示:
// 创建pthread线程,返回0表示成功,否则表示失败
int pthread_create(pthread_t _Nullable * _Nonnull __restrict,
const pthread_attr_t * _Nullable __restrict,
void * _Nullable (* _Nonnull)(void * _Nullable),
void * _Nullable __restrict);
四个参数:
pthread_t*
表示线程标识符指针,pthread_t
是线程的唯一标识符。pthread_attr_t *
表示对线程设置的属性,后面详细介绍。void*(void*)
表示线程启动的线程函数,函数结束后,线程也将终止。void*
表示向线程函数传递的参数。pthread_attr_t
通过pthread_create
函数的第二个参数可以设置线程属性,该属性是pthread_attr_t
结构体,如下所示:
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
初始化线程属性,此时所有线程属性都是默认值,然后通过pthread_create
函数的第二个参数为线程设置定制属性值,最后通过pthread_attr_destroy
销毁线程属性,下面看下各个线程属性。
线程的分离状态决定一个线程以什么样的方式来终止自己,有两种取值:
pthread_join
等待子线程结束。pthread_join
无效),函数运行结束了,线程也就终止了,且终止时自行释放所占用资源。可以通过以下函数设置或者获取线程的分离状态:
线程的调度策略,有三种取值:
SCHED_RR
策略的线程执行时间超过了固定时间片而没有阻塞,而另外的SCHED_RR
或者SCHBD_FIPO
策略的相同甚至更高优先级的线程准备好了,那么当前线程的执行权将被它们抢占。可以通过以下函数设置或者获取线程的调度策略:
线程的调度参数,是一个包含了线程优先级的结构体:
struct sched_param {
// 线程优先级
int sched_priority;
char __opaque[__SCHED_PARAM_SIZE__];
};
线程的优先级范围可以通过以下函数获取:
// 线程的最低优先级,
int sched_get_priority_min(int);
// 线程的最高优先级
int sched_get_priority_max(int);
函数参数是不同的调度策略,例如:SCHED_FIFO、SCHED_RR和SCHED_OTHER,可见不同调用策略下的线程优先级范围可能是不同的。
可以通过以下函数设置或者获取线程的调度参数:
调度策略和线程优先级是一件非常复杂的事情,如果不正确使用,很容易导致死锁,慎用。
线程针对调度策略和调度参数的继承策略,有两个取值:
pthread_attr_setschedpolicy
和pthread_attr_setschedparam
设置的线程调度策略和参数。可以通过以下函数设置或者获取线程的继承策略:
如果需要显式设置线程的调度策略和参数,那么必须在设置之前先将
inheritsched
属性设置为PTHREAD_EXPLICIT_SCHED
。
线程优先级的作用域,即线程间竞争CPU的有效范围,有两个取值:
目前Linux只实现了
PTHREAD_SCOPE_SYSTEM
。
可以通过以下函数设置或者获取线程优先级的作用域:
线程可以共享进程内存空间,同时也有自己私有的堆栈空间。线程内的局部变量分配在私有堆栈上,线程堆栈的大小在线程创建时就确定了,若局部变量占用空间超过栈空间,就会引起coredump。
线程堆栈最小值是PTHREAD_STACK_MIN
,一般是8K或者16K。可以通过pthread_attr_setstacksize
设置堆栈大小,通过pthread_attr_getstacksize
获取堆栈大小。这种情况下,由内核分配和释放堆栈内存。
除此之外,我们还可以自己管理堆栈内存,通过pthread_attr_setstackaddr
设置堆栈地址,通过pthread_attr_getstackaddr
获取堆栈地址。堆栈地址必须以Linux页面大小对齐,可以使用posix_memalign
分配页面对齐的内存。此外,还可以通过pthread_attr_setstack
设置堆栈地址和大小,通过pthread_attr_getstack
获取堆栈地址和大小,下面看一个例子:
int main(){
pthread_attr_t attr;
pthread_attr_init(&attr);
size_t stacksize = -1;
void *stack_addr = nullptr;
// 获取缺省的堆栈地址和大小
pthread_attr_getstack(&attr, &stack_addr, &stacksize);
cout << "default stack addr: " << stack_addr << endl;
cout << "default stack size: " << stacksize << endl;
void *stackAddr = nullptr;
//获取linux页大小
int paseSize = getpagesize();
// 设置的堆栈大小
int size = paseSize * 2;
cout << "paseSize: " << paseSize << endl;
posix_memalign(&stackAddr, paseSize, size);
// 设置新的堆栈地址和大小
pthread_attr_setstack(&attr, stackAddr, size);
// 获取当前堆栈地址和大小
pthread_attr_getstack(&attr, &stack_addr, &stacksize);
cout << "new stack addr: " << stack_addr << endl;
cout << "new stack size: " << stacksize << endl;
pthread_attr_destroy(&attr);
return 0;
}
// 输出为
default stack addr: 0x0
default stack size: 524288
paseSize: 4096
new stack addr: 0x7f7f66002000
new stack size: 8192
上述代码获取的默认的堆栈大小是512K,新设置的堆栈大小是8K。
线程堆栈的保护区大小,即当线程堆栈不够用时,在堆栈的溢出端分配的额外内存。此额外内存的作用与缓冲区一样,可以防止栈溢出。
如果guardsize
为0,则不会为线程提供溢出保护区。如果guardsize
大于零,则会为对应线程提供至少guardsize
字节的溢出保护区。缺省情况下,guardsize
大于零。可以通过以下函数设置或者获取线程堆栈的保护区大小:
pthread_mutex_t
表示互斥量,用于控制同一时刻只能有一个线程访问临界区。
相关函数如下所示:
pthread_mutex_lock
和pthread_mutex_unlock
一定要成对调用。
pthread_cond_t
表示条件变量,用于线程协同(同步)工作。
不管是在指定条件变量上wait,还是唤醒(signal和broadcast)指定条件变量上的线程,都必须先获得对应锁(pthread_mutex_t)。
wait的流程:
signal和broadcast的流程:
pthread_cond_timedwait
使用范例:
// 获取当前时间
struct timeval now;
gettimeofday(&now, nullptr);
// 指定向后延迟5S(绝对时间)
struct timespec spec;
// 秒
spec.tv_sec = now.tv_sec + 5;
// 纳秒
spec.tv_nsec = 0;
// 先加锁
pthread_mutex_lock(&mLock);
// wait到指定的绝对时间:若被提前唤醒,则返回0,否则返回非0
int result = pthread_cond_timedwait(&mCond, &mLock, &spec);
// 释放锁
pthread_mutex_unlock(&mLock);