每个线程有一个线程 ID,线程 ID 只有在它所属的进程上下文中才有意义。
线程的 ID 是用 pthread_t 数据类型来表示的,实现的时候可以用一个结构来代表 pthread_t 数据类型,所以可移植的操作系统实现不能把它作为整数处理。因此需要如下函数对两个线程 ID 进行比较
pthread_t 无符号长整形,此函数用来对两个线程 ID 进行比较
若相等,返回非 0 数值;否则,返回 0
extern int pthread_equal (pthread_t __thread1, pthread_t __thread2);
用结构表示 pthread_t 数据类型的后果是不能用一种可移植的方式打印该数据类型的值。
线程可以通过 pthread_self 函数获得自身的线程 ID
此函数可获得自身的线程 ID
调用线程的线程 ID
extern pthread_t pthread_self (void);
当线程需要识别以线程 ID 作为标识的数据结构时,pthread_self 函数可以与 pthread_equal 函数一起使用。
创建线程,新创建的线程 ID 会被设置成
若成功,返回 0;否则,返回错误编号 tidp 指向的内存单元
extern int pthread_create (pthread_t *__restrict tidp,
const pthread_attr_t *__restrict __attr,
void *(*__start_routine) (void *),
void *__restrict __arg);
当 pthread_creagte 成功返回时,新创建线程的线程 ID 会被设置成 tidp 指向的内存单元。attr 参数用于定制各种不同的线程属性,NULL 表示默认属性。
新创建的线程从 start_rtn 函数的地址开始运行,该函数只有一个无类型指针参数 arg。如果需要向 start_rtn 函数传递的参数有一个以上,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为 arg 参数传入。
创建线程时并不能保证哪个线程会先运行:是新创建的线程,还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。
单个线程可以通过3种方式退出
线程退出,rval_ptr是一个无类型的指针
extern void pthread_exit(void *rval_ptr);
rval_ptr 参数是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程也可以通过 pthread_join 函数访问到这个指针
进程中的其他线程也可以通过调用 pthread_join 函数访问 rval_ptr 这个指针
若成功,返回 0 ;否则,返回错误编号
extern int pthread_join (pthread_t __th, void **__thread_return);
调用线程将一直阻塞,直到指定的线程调用 pthread_exit 、从启动例程中返回或者被取消。
pthread_join 自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join 调用就会失败,返回 EINVAL。
pthread_create 和 pthread_exit 函数的无类型指针参数可以传递的值不止一个,这个指针可以传递包含复杂信息的结构的地址,但是注意,这个结构所使用的内存在调用者完成调用以后必须仍然是有效的。例如,在调用线程的栈上分配了该结构,那么其他的线程在使用这个结构时内存内容可能已经改变了。又如,线程在自己的栈上分配了一个结构,然后把指向这个结构的指针传递给 pthread_exit ,那么调用 pthread_join 的线程试图使用该结构时,这个栈可能已经撤销,这块内存也另做他用。
线程可以通过调用 pthread_cancel 函数来取消同一进程中的其他线程
extern int pthread_cancel (pthread_t __th);
默认情况下,pthread_cancel 函数会使得由 tid 标识的线程的行为标识为如同调用了参数为 PTHREAD_CREATELED 的 pthread_exit 函数,但是,线程可以选择忽略取消或者控制如何被取消。
注意 pthread_cancel 并不等待线程终止,它仅仅提出请求。
在默认情况下,线程的终止状态会保存直到对线程调用 pthread_join。如果线程已经被分离,线程的底层存储资源可以在线程终止时立即被回收。在线程分离后,我们不能用 pthread_join 函数等待它的终止状态,因为对分离状态的线程调用 pthread_join 会产生未定义行为。可以调用 pthread_detach 分离线程。
extern int pthread_detach (pthread_t __th)
当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。当一个线程修改变量时,其他线程在读取这个变量时可能会看到一个不一致的值。在变量修改时间多于一个存储器访问周期的处理器结构中,当存储器读与存储器写这两个周期交叉时,这种不一致就会出现。
两个或多个线程视图在同一时间修改同一变量时,也需要进行同步。考虑变量增量操作的情况,增量操作通常分解为以下3步:
可以使用 pthread 的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。
在使用互斥变量以前,必须首先对它进行初始化,可以把它设置为常量 PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量),也可以调用如下函数初始化
初始化 pthread_mutex_t
extern int pthread_mutex_init (pthread_mutex_t *__mutex, const pthread_mutexattr_t *__mutexattr);
销毁 pthread_mutex_t
extern int pthread_mutex_destroy(pthread_mutex_t *__mutex);
要使用默认的属性初始化互斥量,只需把 attr 设置为 NULL
对互斥量进行加锁
extern int pthread_mutex_lock (pthread_mutex_t *__mutex);
释放
extern int pthread_mutex_unlock (pthread_mutex_t *__mutex);
如果不希望被阻塞,可以使用以下函数尝试对互斥量进行加锁。如果处于未锁住状态,那么将锁住互斥量,不会出现阻塞直接返回0,否则就会失败,不能锁住互斥量。
尝试对互斥量进行加锁
extern int pthread_mutex_trylock (pthread_mutex_t *__mutex);
当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock 互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock 函数与 pthread_mutex_lock 是基本等价的,但是在达到超时时间值时, pthread_mutex_timedlock 不会对互斥量进行加锁,而是返回错误码。
指定在时间 x 之前可以阻塞等待,
extern int pthread_mutex_timedlock (pthread_mutex_t *__restrict __mutex,
const struct timespec *__restrict
__abstime);
互斥锁要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁。
读写锁可以有3种状态,读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁
extern int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock,
const pthread_rwlockattr_t *__restrict
__attr);
extern int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock);
读写锁非常适合于对数据 结构读的次数原大于写的情况。
读写模式分不同的锁
读模式下锁定读写锁
extern int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock);
写模式下锁定读写锁
extern int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock);
extern int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock);
读写锁条件版本
extern int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock);
extern int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock);
带有超时的读写锁
extern int pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock,
const struct timespec *__restrict __abstime);
extern int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock,
const struct timespec *__restrict __abstime);
条件变量是线程可用的另一种同步机制。条件变量给多个线程提供了一个会合场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件变量,允许线程以无竞争的方式等待特定的条件发生
条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件
初始化
extern int pthread_cond_init (pthread_cond_t *__restrict __cond,
const pthread_condattr_t *__restrict __cond_attr);
extern int pthread_cond_destroy (pthread_cond_t *__cond);
传递给 pthread_cond_wait 的互斥量对条件进行保护。调用者把锁住的互斥量传给函数,函数然后自动把调用线程放到等待条件的线程列表上,对互斥量解锁。
而 pthread_cond_timedwait 带有超时时间。超时值指定了我们愿意等待多长时间,这个时间值是一个绝对数而不是相对数。可以使用 clock_gettime 函数获取 timespec 结构表示的当前时间。
等待
extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex);
extern int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,
pthread_mutex_t *__restrict __mutex,
const struct timespec *__restrict __abstime);
有两个函数可以用于通知线程条件已经满足。pthread_cond_signal 函数至少能唤醒一个等待条件的线程,而 pthread_cond_broadcast 函数则能唤醒等待该条件的所有线程。
extern int pthread_cond_signal (pthread_cond_t *__cond);
extern int pthread_cond_broadcast (pthread_cond_t *__cond);
结合使用条件变量和互斥变量对线程进行同步的主要示例
struct msg {
struct msg *m_next;
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void process_msg(void) {
struct msg *mp;
for (;;) {
pthread_mutex_lock(&qlock);
while (workq == NULL) {
pthread_cond_wait(&qready, &qlock);
mp = workq;
workq = mp->m_next;
pthread_mutex_unlock(&qlock);
// todo
}
}
}
void enqueue_msg(struct msg *mp) {
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&qready);
}
我们用互斥量保护条件,在 while 循环中判断条件。把消息放到工作队列时,需要占有互斥量,但在给等待线程发信号时,不需要占有互斥量。只有线程在调用 pthread_cond_signal 之前把消息从队列中拖出了,就可以在释放互斥量以后完成这部分工作。
自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。
自旋锁可用于以下情况:锁被持有的时间短,而线程不希望在重新调度上花费太多的成本。
自选锁
extern int pthread_spin_init (pthread_spinlock_t *__lock, int __pshared);
extern int pthread_spin_destroy (pthread_spinlock_t *__lock);
使用 pthread_spin_lock 或 pthread_spin_trylock 对自旋锁进行加锁,前者在获取锁之前一直自旋,后者如果不能获取锁,就立即返回错误。
extern int pthread_spin_lock (pthread_spinlock_t *__lock);
extern int pthread_spin_trylock (pthread_spinlock_t *__lock);
extern int pthread_spin_unlock (pthread_spinlock_t *__lock);
不管是 pthread_spin_lock 还是 pthread_spin_trylock,返回值为 0 的话就表示自旋锁被加锁。需要注意,不要调用在持有自旋锁情况下可能会进入休眠模式的函数。如果调用了这些函数,会浪费 cpu 资源,因为其他线程需要获取自旋锁需要等待的时间就延长了。
屏障是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从该点继续执行。
pthread_join 函数就是一种屏障,允许一个线程等待,直到另一个线程退出。
初始化屏障时,可以使用 count 参数指定,在允许所有线程继续运行之前,必须达到屏障的线程数目。
extern int pthread_barrier_init (pthread_barrier_t *__restrict __barrier,
const pthread_barrierattr_t *__restrict
__attr, unsigned int __count);
extern int pthread_barrier_destroy (pthread_barrier_t *__barrier);
使用下列函数来表明,线程已完成工作,准备等所有其他线程赶上来
extern int pthread_barrier_wait (pthread_barrier_t *__barrier);