大纲
- gerneral
- thread opreation api
- mutex
- condition value
一、 Introduction
1980-90s,存在数种不同的线程接口,1995年POSIX.1 c对POSIX线程API进行标准化,后来被SUSv3接受。
1.1 线程数据类型
数据类型 | 描述 |
---|---|
pthread_t | 线程ID |
pthread_attr_t | 线程属性 |
pthread_mutex_t | 互斥变量 |
pthread_mutexattr_t | |
pthread_cond_t | 条件变量 |
pthread_condattr_t | |
pthread_key_t | 线程特有数据的键key |
pthread_once_t | 一次性初始化控制上下文 |
二、 线程 Pthread
int pthread_create (pthread_t *__restrict __newthread,
const pthread_attr_t *__restrict __attr,
void *(*__start_routine) (void *),
void *__restrict __arg) ; // 创建新线程
void pthread_exit (void *__retval) ; // 终止本线程
int pthread_join (pthread_t __th, void **__thread_return); // 连接线程th,只能以阻塞态执行
int pthread_detach (pthread_t __th); // 线程分离
pthread_t pthread_self (void); // 获取本线程ID
int pthread_equal (pthread_t __thread1, pthread_t __thread2); // 比较线程ID,是否相等
int pthread_yield (void); // 主动让出CPU,触发线程调度。
int pthread_cancel (pthread_t __th); // 终止线程th
- 线程分离:分离后,不能再通过join()来连接该线程。
- yield()
- 调用之后,本线程将被放在end of the queue for its static priority.
- 如果没有更高/同等优先级的线程,那么还是继续运行该线程。但是这中间有线程调度,故而有context切换。
- join()
- 线程之间是对等的,故而没有父子之分。不必像process那样,只能父wait()子
- 无法"连接任意线程",只能连接它知道的线程。
- 不能以non-block方式连接。
- pthread_t
- 应该把pthread_t当作一个对象看待,使用pthread_self(), pthread_equal()等方法,避免不可移植的操作。
- SUSv3并未要求pthread_t是个标量,因而也有系统定义为struct。在NPTL(Native POSIX Thread Library)中,pthread_t是一个强制转化unsigned long int的指针。
三、 互斥量 mutex
作用:保护对共享变量的访问
mutex : mutual exclusion
pthread_mutex_t mtx = PTHREAD_MUTEX_INITTIALIZER;
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
- 只有mutex的所有者(调用lock的线程)才能unlock该mutex。
- 如果mutex已经locked,再调用lock()的线程会一直阻塞,直到mutex available。
- 如果多个线程等待获取mutex,那么当mutex释放时,不能确定哪个线程将如愿以偿
- lock()自己线程已经锁定的mutex,可能会导致自锁(在Linux中默认如此);也可能返回错误EDEADLK(视情况而定)。
- unlock() 一个未被locked mutex或其他线程lock 的mutex,都会返回错误。
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_timedlock (pthread_mutex_t *__restrict __mutex,
const struct timespec *__restrict __abstime);
- trylock() 当mutex已经锁定时返回EBUSY错误
- timedlock() 阻塞时间超过abstime时返回ETIMEDOUT错误
3.1 动态分配的 mutex
静态初始值PTHREAD_MUTEX_INITIALIZER,只能用于初始化:静态分配且携带默认attribute 的mutex。
其他情况下,必须调用pthread_mutex_init()进行动态初始化。
int pthread_mutex_init (pthread_mutex_t *__mutex,
const pthread_mutexattr_t *__mutexattr);
- 若mutexattr==NULL,那么mutex各属性将使用默认值。
int pthread_mutex_destroy (pthread_mutex_t *__mutex);
- 对于动态分配于堆中、或自动分配于栈中的互斥量,当不再需要时,应该用destroy()将其摧毁。
- 用destroy()销毁的mutex,可以用init()重新初始化。
3.2 mutex的类型
对于以下行为,结果取决于mutex的类型:
- 同一线程对同一mutex连续两次lock
- 线程unlock自己不拥有的mutex
- 线程unlock没有锁定的mutex
PTHREAD_MUTEX_NORMAL
- 该类型不具有死锁自检功能
- 对于两种错误的unlock行为,行为不确定(Linux中,两种行为都会成功)
PTHREAD_MUTEX_ERRORCHECK
- 对所有错误都会进行检查,并返回对应的errno
PTHREAD_MUTEX_RECURSIVE
- 有一个计数器,允许同一线程多次加锁,解锁至计数器==0时才会释放。
非标准的静态初始值
- 例如PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, PTHREAD_MUTEX_DEFAULT
四、 条件变量 condition value
作用:通知状态的改变
4.1 general
pthread condition提供如下函数,用于线程间同步的通知:
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);//创建一个condition的内核变量
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);//等待内核变量变为signaled
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);//在指定时间内等待内核心变量变为signaled
int pthread_cond_destroy(pthread_cond_t *cond);//“析构”一个condition的内核变量
int pthread_cond_signal(pthread_cond_t *cond);//把内核变量置为signaled,指定一个等待线程激活,但不能指定特定线程
int pthread_cond_broadcast(pthread_cond_t *cond);//把内核变量置为signaled,指定所有等待线程激活
- 发送信号:signal() 通知一个等待线程;broadcast() 通知所有等待线程
- 等待:wait() 等待通知,获得通知前一直阻塞线程;timedwait(),阻塞等待,超过时间则返回ETIMEOUT错误
4.2 静态初始化和动态初始化
与mutex 类似
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 静态初始化
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr); // 动态初始化
4.3 condition value 总是与一个 mutex 相关
- 条件变量总是和互斥量结合使用。条件变量发出通知,互斥量提供对共享变量的互斥访问。
- wait(&cond, &mutex), signal(&cond). 多个线程wait() 同一个cond时,它们指定的mutex必须相同。因为wait()会将cond和mutex动态绑定,所以,多个线程指定的mutex不同时,结果不可预料。
signal()使用案例
pthread_mutex_lock(&mtx);
// 在此修改共享变量, mutex 起保护共享变量作用
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond); // 唤醒wait()线程
- 最后两步(解锁、通知),顺序可以颠倒。不过“先signal后unlock”会出现其他线程已唤醒,但是mutex还没法获取的现象,导致重新阻塞等mutex。
wait()使用案例
pthread_mutex_lock(&mtx);
while(x) // 在这里check 共享变量状态,如果不满足,则进入循环体
{
pthread_mutex_wait(&cond, &mtx); // 注意wait()上下文环境,理解wait()的操作
}
// 这里使用共享变量
pthread_mutex_unlock(&mtx); // 因为wait()自动lock了mutex,所以这里要unlock。
- wait(&cond, &mutex) 执行的操作
- 解锁mutex。(默认 mutex此前是被本线程locked状态)
- 堵塞线程,直到获得cond通知
- 锁定mutex。如果mutex没有锁定成功(被其他线程抢先锁定),则继续阻塞等待mutex。
- wait() 这三步是原子操作。
4.4 动态分配的要销毁
int pthread_cond_destroy(pthread_cond_t *cond);