@ltlovezh
2020-02-15T09:50:50.000000Z
字数 6161
阅读 1483
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: 0x0default stack size: 524288paseSize: 4096new stack addr: 0x7f7f66002000new 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,否则返回非0int result = pthread_cond_timedwait(&mCond, &mLock, &spec);// 释放锁pthread_mutex_unlock(&mLock);