线程标识
每一个线程都有一个ID,用下面的方法获得线程ID
pthread_t pthread_self(void);
返回调用线程ID
比较两个线程ID是否相等
int pthread_equal(pthread_t t1, pthread_t t2);
相等返回非0,否则0
创建一个新线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
thread返回线程ID
attr是线程的属性,暂时可以指定为NULL(默认属性)
start_routine新线程的执行函数
arg传给新线程的参数
注意:在新线程中尽量不要使用erron这个全局变量(虽然每个线程其实都提供了errno的副本),新线程会继承调用线程的信号屏蔽字,但是新线程的挂起信号集被清除
线程的终止
1.在线程中调用exit,_Exit,_exit函数会让整个进程都终止
2.如果线程收到了默认终止进程的信号,则整个进程终止
3.线程从执行函数返回,返回值是线程的退出值
4.线程被同一个进程中的其他线程取消
5.线程调用pthread_exit函数退出
void pthread_exit(void *retval);
retva是线程的返回值
请求取消同一个进程中的其他线程(只是请求,线程不一定会终止)
int pthread_cancel(pthread_t thread);
注意:只有线程执行到了取消点线程才会被取消,该函数一般不应该使用
阻塞等待同一个进程中的线程结束并获取其返回值
int pthread_join(pthread_t thread, void **retval);
如果线程已经被join过则该函数出错返回,如果线程已经结束,则该函数就不阻塞
安装线程退出清理函数
void pthread_cleanup_push(void (*routine)(void *), void *arg);
当遇到如下三种情况时候被安装的routine函数会被执行(可以安装多个,但执行顺序和安装顺序相反):
1.线程调用pthread_exit函数时
2.响应取消请求时
3.调用pthread_cleanup_pop(非零)时
注意:如果是调用return从线程返回时不会执行安装的线程清理函数
执行线程清理函数
void pthread_cleanup_pop(int execute);
如果execute是非0值,则最近安装的线程清理函数被执行
如果execute是0,则取消最近安装的线程清理函数,不执行
注意:pthread_cleanup_push和pthread_cleanup_pop一定要成对使用
把线程设置为分离状态
int pthread_detach(pthread_t thread);
置为分离状态的线程结束后内核会回收线程所占的资源,这种线程是不能被join的
线程同步
线程同步的必要性
多个线程同时访问共享数据会发生错误,因此需要用锁来锁住代码的临界区
互斥量
互斥量的初始化
一种方法是静态初始化互斥量pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
另外一种方法是调用如下函数
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
如果调用上面的函数初始化互斥量则需要用下面的函数来释放互斥量
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥量加锁
如果加锁不成功便会阻塞等待
int pthread_mutex_lock(pthread_mutex_t *mutex);
注意:如果一个信号处理函数打断了pthread_mutex_lock(),该函数会自动的重新执行
如果加锁不成功出错返回
int pthread_mutex_trylock(pthread_mutex_t *mutex);
如果加锁不成功则阻塞,但当前时间等于abs_timeout时还没获得互斥量,函数出错返回
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
互斥量解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
互斥量使用不当会死锁
1.自死锁
2.ABBA死锁
避免死锁的一般做法
1.如果多个线程在访问共享资源的时候需要获得多把锁,则所有的线程的加锁顺序要一致
2.使用pthread_mutex_trylock
条件变量
条件变量的初始化
一种方法是静态初始化pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
另外一种方法是调用如下函数
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
如果用了上面的函数对条件变量进行了初始化,则需要用下面的函数进行反初始化
int pthread_cond_destroy(pthread_cond_t *cond);
在该条件变量上睡眠等待其他线程工作完成通知(如果已经通知过了则不再睡眠)
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
注意:如果信号处理函数打断了pthread_cond_wait()(参见POSIX线程-条件变量),该函数要么自动重新自行(linux是这样实现的),或者返回0(这时应用要检查返回值,判断是否为假唤醒)。
在该条件变量上睡眠等待其他线程工作完成通知(如果已经通知过了则不再睡眠),但如果当前时间等于abstime时还没有等到完成信号,则函数出错返回
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
注意:在使用pthread_cond_wait和pthread_cond_wait_timedwait时,一定要先获得mutex锁,如果这两个函数由于条件变量cond要睡眠则会先释放mutex锁再睡眠,当然函数返回时会再次加锁mutex,那么在锁住mutex和调用这两个函数之间我们就可以判断一个条件(判断是否要调用这两个函数来睡眠等待),比如:
pthread_mutex_lock(&mutex);
while(a != 1)
pthread_cond_wait(&cond, &mutex)
pthread_mutex_unlock(&mutex)
通知其他线程工作完成(即发送完成信号)
唤醒至少一个由于该条件变量而睡眠的线程
int pthread_cond_signal(pthread_cond_t *cond);
唤醒所有由于该条件变量而睡眠的线程(但有些线程可能会因为得不到锁而继续睡眠)
int pthread_cond_broadcast(pthread_cond_t *cond);
注意:pthread_cond_wait函数返回的条件是收到唤醒信号并且成功加锁,不然会继续睡眠
注意:多个线程调用pthread_cond_wait(&cond, &mutex)的时候,不要在一个条件变量上用多个锁(实测在调用pthread_cond_broadcast时候会出问题)
自旋锁
如果加锁不成功以忙等的方式等待锁
使用自旋锁的目的是为了节省线程调度的开销,但在用户太程序中一般用不着,因为现在一般的互斥量都是先自选一小会然后才有可能睡眠
屏障
屏障用来使多个线程在某一点等待所有线程都到达这一点,然后继续运行
初始化屏障
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count);
count是等待线程的个数
反初始化屏障
int pthread_barrier_destroy(pthread_barrier_t *barrier);
通知其他线程当前线程已经到达,等所有线程都到达后才会退出
int pthread_barrier_wait(pthread_barrier_t *barrier);
调用这个函数的线程中有一个会返回PTHREAD_BARRIER_SERIAL_THREAD,其他线程都返回0