了解 POISX Thread
什么是 POSIX Threads
POSIX Threads (通常被缩写为 Pthreads)是 POSIX (可移植操作系统接口,Portable Operating System Interface)的线程标准,定义了创建和操作线程的一套 API。
Pthreads
实现 POSIX 线程标准的库常被称作 Pthreads
, Pthreads
定义了一套 C 语言的类型、函数与常量,它以 pthread.h
头文件和一个线程库实现。
Pthreads API 中大致共有 100个 函数调用,全都以 pthread_
头,并可以分为以下四类:
- 线程管理,例如创建线程,等待(join)线程,查询线程状态等。
- 互斥锁(Mutex):创建、摧毁、锁定、解锁、设置属性等操作
- 条件变量(Condition Variable):创建、摧毁、等待、通知、设置与查询属性等操作
- 使用了互斥锁的线程间的同步管理
数据类型
pthread_t
pthread_t
是线程句柄。出于可移植目的,不能把它作为整数处理,应使用函数 pthread_equal()
对两个线程 id 进行比较。获取自身所在线程 id 使用函数为 pthread_self()
。
pthread_attr_t
pthread_attr_t
是线程属性。主要包括 scope
属性、detach
属性、堆栈地址、堆栈大小、优先级。主要属性的意义如下:
-
__detachstate
,表示新线程是否与进程中其他线程脱离同步。- 设置为
PTHREAD_CREATE_DETACHED
, 则新线程不能用pthread_join()
来同步,且在退出时自行释放所占用的资源。 - 默认为
PTHREAD_CREATE_JOINABLE
。可以在线程创建并运行以后用pthread_detach()
来设置 - 设置为
PTHREAD_CREATE_DETACHED
状态,不论是创建时设置还是运行时设置,则不能再恢复到PTHREAD_CREATE_JOINABLE
状态。
- 设置为
-
__schedpolicy
,表示新线程的调度策略,默认值为SCHED_OTHER
,后两种调度策略仅对超级用户有效,运行时可以用过pthread_setschedparam()
来改变-
SCHED_OTHER
,正常、非实时 -
SCHED_RR
,实时、轮转法 -
SCHED_FIFO
,实时、先入先出
-
-
__schedparam
,一个struct sched_param
结构,目前仅有一个sched_priority
整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR
或SCHED_FIFO
)时才有效,并可以在运行时通过pthread_setschedparam()
函数来改变,默认为0。系统支持的最大和最小的优先级值可以用函数sched_get_priority_max
和sched_get_priority_min
得到。 -
__inheritsched
,默认为PTHREAD_EXPLICIT_SCHED
。-
PTHREAD_EXPLICIT_SCHED
表示新线程使用显式指定调度策略和调度参数(即attr中的值), -
PTHREAD_INHERIT_SCHED
而后者表示继承调用者线程的值。
-
-
__scope
,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。-
PTHREAD_SCOPE_SYSTEM
,表示与系统中所有线程一起竞争CPU时间, -
PTHREAD_SCOPE_PROCESS
后者表示仅与同进程中的线程竞争CPU。
-
pthread_barrier_t
同步屏障数据类型
pthread_mutex_t
pthread_mutex_t
是线程互斥锁数据类型,有两种方法创建互斥锁:
-
静态方式
POSIX 定义了一个宏PTHREAD_MUTEX_INITIALIZER
来静态初始化互斥锁, -
动态方式
采用pthread_mutex_init()
函数来初始化互斥锁,其中mutexattr
用于指定互斥锁属性,如果为NULL
则使用默认属性:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t*mutexattr)
pthread_mutexattr_t
互斥锁的属性 pthread_mutexattr_t
在创建锁的时候指定,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。在 iOS 中有以下几个类型可选:
-
PTHREAD_MUTEX_NORMAL
这是默认值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。 -
PTHREAD_MUTEX_ERRORCHECK
检错锁,如果同一个线程请求同一个锁,则返回EDEADLK。 -
PTHREAD_MUTEX_RECURSIVE
嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock
解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
pthread_cond_t
pthread_cond_t
是条件变量数据类型,条件变量和互斥锁一样,都有静态和动态两种创建方式,
-
静态方式
使用PTHREAD_COND_INITIALIZER
常量进行初始化 -
动态方式
调用pthread_cond_init()
函数
操作线程函数
创建一个线程
int pthread_create(pthread_t _Nullable * _Nonnull __restrict,
const pthread_attr_t * _Nullable __restrict,
void * _Nullable (* _Nonnull)(void * _Nullable),
void * _Nullable __restrict);
终止当前线程
pthread_exit(void)
中断一个线程
int pthread_cancel(pthread_t)
阻塞当前的线程
阻塞当前的线程,直到另外一个线程运行结束
int pthread_join(pthread_t , void * _Nullable * _Nullable)
__DARWIN_ALIAS_C(pthread_join);
向指定ID的线程发送一个信号
向指定ID的线程发送一个信号,如果线程不处理该信号,则按照信号默认的行为作用于整个进程。信号值0为保留信号,作用是根据函数的返回值判断线程是不是还活着。
int pthread_kill(pthread_t threadId,int signal);
线程属性函数
初始化线程属性变量
int pthread_attr_init(pthread_attr_t *);
设置/获取线程属性变量的 detachstate
属性
int pthread_attr_setdetachstate(pthread_attr_t *, int);
int pthread_attr_getdetachstate(const pthread_attr_t *, int *);
设置/获取 scope
int pthread_attr_setscope(pthread_attr_t *, int);
int pthread_attr_getscope(const pthread_attr_t * __restrict, int * __restrict);
设置/获取 schedparam
int pthread_attr_setschedparam(pthread_attr_t * __restrict,
const struct sched_param * __restrict);
int pthread_attr_getschedparam(const pthread_attr_t * __restrict,
struct sched_param * __restrict);
销毁线程属性
int pthread_attr_destroy(pthread_attr_t *);
互斥锁函数
初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t*mutexattr)
销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *);
加锁
int pthread_mutex_lock(pthread_mutex_t *);
int pthread_mutex_trylock(pthread_mutex_t *);
解锁
int pthread_mutex_unlock(pthread_mutex_t *);
条件变量函数
初始化条件变量
int pthread_cond_init(
pthread_cond_t * __restrict,
const pthread_condattr_t * _Nullable __restrict)
__DARWIN_ALIAS(pthread_cond_init);
销毁条件变量
int pthread_cond_destroy(pthread_cond_t *);
等待条件变量的特殊条件发生
pthread_cond_wait()
必须与一个 pthread_mutex
配套使用。该函数调用实际上依次做了3件事:
- 对当前
pthread_mutex
解锁 - 把当前线程挂起到当前条件变量的线程队列
- 被其它线程的信号唤醒后对当前
pthread_mutex
申请加锁。
int pthread_cond_wait(pthread_cond_t * __restrict,
pthread_mutex_t * __restrict) __DARWIN_ALIAS_C(pthread_cond_wait);
发送一个信号
pthread_cond_signal
发送一个信号给正在当前条件变量的线程队列中处于阻塞等待状态的线程,使其脱离阻塞状态,唤醒后继续执行。如果没有线程处在阻塞等待状态,pthread_cond_signal
也会成功返回。一般只给一个阻塞状态的线程发信号。假如有多个线程正在阻塞等待当前条件变量,则根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但 pthread_cond_signal
在多处理器上可能同时唤醒多个线程,当只能让一个被唤醒的线程处理某个任务时,其它被唤醒的线程就需要继续 wait
。
int pthread_cond_signal(pthread_cond_t *);
工具函数
查询线程自身线程标识号
pthread_t pthread_self(void);
比较两个线程标识
int pthread_equal(pthread_t _Nullable, pthread_t _Nullable);
执行一次
某些需要仅执行一次的函数。其中第一个参数为 pthread_once_t
类型,是内部实现的互斥锁,保证在程序全局仅执行一次。
int pthread_once(pthread_once_t *, void (* _Nonnull)(void));
线程私有存储(Thread-local storage,简写 tls)
Thread-local storage 是操作系统为线程单独提供的私有空间,只有有限的容量。通常通过 pthread
库中的函数实现:
创建 key
分配用于标识进程中线程特定数据的 pthread_key_t
类型的键
int pthread_key_create(pthread_key_t *, void (* _Nullable)(void *));
销毁现有线程特定数据 Key
int pthread_key_delete(pthread_key_t);
为指定线程的特定数据键设置绑定的值
extern int pthread_setspecific(unsigned long, const void*);
获取绑定的值
extern void *pthread_getspecific(unsigned long);
objc tls 实现
typedef pthread_key_t tls_key_t;
static inline tls_key_t tls_create(void (*dtor)(void*)) {
tls_key_t k;
pthread_key_create(&k, dtor);
return k;
}
static inline void *tls_get(tls_key_t k) {
return pthread_getspecific(k);
}
static inline void tls_set(tls_key_t k, void *value) {
pthread_setspecific(k, value);
}