APUE - 线程

线程标识

就像每个进程有一个进程 ID 一样,每个线程也有一个线程 ID。进程 ID 在整个系统中是唯一的,但线程 ID 不同,线程 ID 只在它所属的进程环境中有效。

对两个线程 ID 进行比较:

#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);

线程可以通过调用 pthread_self 函数获得自身的线程 ID。

#include <pthread.h>
pthread_t pthread_self(void);

线程的创建

新增的线程可以通过调用 pthread_create 函数创建。

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
                   const pthread_attr_t *restrict attr,
                   void *(*start_rtn)(void), void *restrict arg);

线程创建时并不能保证哪个线程会先运行:是新创建的线程还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。

线程终止

如果进程中的任一线程调用了 exit、_Exit 或者 _exit,那么整个进程就会被终止。与此类似,如果信号的默认动作是终止进程,那么,把该信号发送到线程会终止整个进程。

单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流。

  1. 线程只是从启动例程中返回,返回值是线程的退出码。
  2. 线程可以被同一进程中的其他线程取消。
  3. 线程调用 pthread_exit。

线程通过调用 pthread_exit 函数终止执行。

#include <pthread.h>
void pthread_exit(void *rval_ptr);

进程中的其他线程可以通过调用 pthread_join 函数访问到 rval_ptr 指针。

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);

调用线程将一直阻塞,直到指定的线程调用 pthread_exit、从启动例程中返回或者被取消。

线程可以通过调用 pthread_cancel 函数来请求取消同一进程中的其他线程。

#include <pthread.h>
int pthread_cancel(pthread_t tid);

线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序。线程可以建立多个清理处理程序,它们的执行顺序与它们注册时的顺序相反。

#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);

pthread_detach 调用可以用于使线程进入分离状态。

#include <pthread.h>
int pthread_detach(pthread_t tid);

线程同步

互斥量

互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。

#include <pthread.h>
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);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

读写锁

读写锁可以有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                        const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
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);

条件变量

条件变量是线程可用的另一种同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
                      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);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                           pthread_mutex_t *restrict mutex,
                           const struct timespec *restrict timeout);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

struct timespec {
    time_t  tv_sec;     /* seconds */
    long    tv_nsec;    /* nanoseconds */
};