【线程标识】
每个进程都有一个进程ID,每个线程也有一个线程ID。进程ID在整个系统中都是唯一的,但是线程ID不同,线程ID只有在它所属的进程的上下文中才有意义。
#include <pthread.h>
/*不同系统对于pthread_t结构定义不同,因此不能直接比较*/
int pthread_equal(pthread_t tid1, pthread_t tid2);
/*可以获取当前线程的ID*/
pthread_t pthread_self(void);
【线程创建】
#include <pthread.h>
/*
*return :0 success
*return :errno fail
*/
int pthread_create(pthread_t *restrict tidp,
const pthread_atrr *restrict attr,
void *(start_rtn)(void *),
void *restrict arg);
【线程终止】
如果进程中的任意线程调用了exit、_Exit、_exit,那么整个进程就会终止。
单个线程可以通过以下三种方式退出,因此可以在不终止整个进程的情况下,停止他的控制流
1.线程可以简单的从启动例程中返回,返回值是线程的退出码。
2.线程可以被统一进程的其他线程取消
3.线程调用pthread_exit
#include <pthread.h>
/*rval_ptr是一个无类型指针,与传给启动例程的单个参数类似,进程中的其他线程可以通过调用pthread_join函数访问到这个指针。*/
void pthread_exit(void *rval_ptr);
int pthread_join(pthread_t thread, void **rval_ptr);
调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程返回或者取消。如果线程简单从启动例程返回,rval_ptr就包含返回码。如果线程被取消,rval_ptr指定的内存单元就被设置为PTHREAD_CANCELED。
可以通过调用pthread_join自动将线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,则调用该函数会失败,返回EINVAL。
也可以把rval_ptr设置为NULL,这样调用pthread_join可以等待指定的线程终止,但不获取终止状态。
#include <pthread.h>
/*线程可以通过调用pthread_cancel来请求取消统一进程的其他线程*/
int pthread_cancel(pthread_t tid);
【线程清理程序】
线程退出时可以调用设置的函数,这与进程使用atexit函数安排退出是一样的。这样的函数被称为线程清理程序(thread cleanup handler)。一个线程可以建立多个清理程序。处理程序记录在栈中,即执行顺序和注册顺序相反。
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
当线程执行以下动作时,rtn是由pthread_cleanup_push调度的,调用时只有一个参数:
1.调用pthread_exit时
2.响应取消请求时
3.用非零参数调用pthread_cleanup_pop时
如果execute参数设置为0,清理函数不被调用。不管发生哪种情况,pthread_clean_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序。
这些函数有一个限制,由于他们可以实现为宏,所以必须在与线程相同的作用域中以匹配对的形式使用。pthread_cleanup_push的宏定义可以包含字符,此时,pthread_cleanup_pop中要有对应的匹配字符。
【P V P】
进程原语 | 线程原语 | 描述 |
fork | pthread_create | 创建新的控制流 |
exit | pthread_exit | 从现有控制流退出 |
waitpid | pthread_join | 从控制流中获取退出状态 |
atexit | pthread_cleanup_push | 注册在退出控制流时调用的函数 |
getpid | pthread_self | 获取控制流ID |
abort | pthread_cancel | 请求控制流的非正常退出 |
在默认情况下,线程的终止状态会保存到直到对这个线程调用pthread_join。但是如果线程已经被分离,则底层存储资源可以再线程终止时立即被收回。在线程被分离后,不能采用pthread_join获取终止状态,否则会出现未定义行为。
可以使用pthread_detach分离线程。
#include <pthread.h>
int pthread_detach(pthread_t tid);
【线程同步-互斥量】
互斥变量用pthread_mutex_t数据结构表示,使用前,必须对它进行初始化,可以设置为PTHREAD_MUTEX_INITIALIZER(静态分配的互斥量),或者调用pthread_mutex_init进行初始化。如果动态分配互斥量,释放内存前需要调用pthread_mutex_destroy.
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destory(pthred_mutex_t *mutex);
对互斥量进行加锁,需要调用pthread_mutex_lock。如果已经加锁,调用线程将阻塞到互斥量被解锁。对互斥量解锁,需要调用pthread_mutex_unlock。
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock对互斥量尝试加锁,如果调用pthread_mutex_trylock时互斥量处于未锁状态,那么将锁住互斥量,不会出现阻塞直接返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,返回EBUSY。
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t mutex);
int pthread_mutex_trylock(pthread_mutex_t mutex);
int pthread_mutex_unlock(pthread_mutex_t mutex);
利用pthread_mutex_timedlock来避免永久阻塞。
#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr);
【线程同步-读写锁】
读写锁三种状态:读模式加锁,写模式加锁,不加锁,提高并发性。
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);/**default:NULL/
int pthread_rwlock_destory(pthread_rwlock_t *restrict rwlock);
/*lock unlock*/
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
/*带有超时的读写锁*/
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock
const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
const struct timespec *restrict tsptr);
【线程同步-自旋锁】
自旋锁和互斥量类似,但是它不是通过休眠来使进程阻塞,而是在获取锁之前一直处于忙等的阻塞状态(自旋)。
自旋锁用于以下情况:
锁被持有的时间短,而且线程不希望在重新调度上花费太多成本。
自旋锁通常作为底层原语用于实现其他类型的锁。
自旋锁在阻塞到可用这段时间,CPU是不能做其他事情的,因此自旋锁只能持有一小段时间。
自旋锁在非抢占式内核中非常有用:提供互斥机制;阻塞中断,这样中断处理程序就不会让系统陷入死锁,因为他需要获取已经被加锁的自旋锁。在分时调度用户层中,当时间片到期,或者具有更高优先级的线程就绪变为可运行时,用户线程就会被取消。如果线程拥有自旋锁,他就会进入休眠状态,阻塞在锁上的其他线程自旋的时间就会加长。
# include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destory(pthread_spinlock_t *lock);
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
【线程同步-屏障barrier】
屏障是用户协调多个线程并行工作的同步机制,屏障允许每个进程等待,知道所有的合作线程都到达某一点,然后从该点继续执行。pthread_join函数就是一种屏障:允许一个线程等待,直到另一个线程退出。
#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
const pthread_barrierattr_t *restrict attr,
unsigned int count);
int pthread_barrier_destory(pthread_barrier_t *restrict barrier);
/*线程已经完成工作,准备等其他所有线程赶上来*/
int pthread_barrier_wait(pthread_barrier_t *barrier);
调用了pthread_barrier_wait的线程在屏障计数未满足条件时,会进入休眠状态,如果该线程是最后一个调用该函数的,就满足了计数值,所有线程就会被唤醒。
一旦到达屏障计数值,而且线程处于非阻塞状态,屏障就可以被重用。