Preface · 序
在现代计算机中,操作系统一般都会支持多进程(Process)以及多线程(Thread)技术,使得其可以同时运行多个程序且效率更高。而我们在开发 iOS app 中也时常需要利用到这些特性,以为用户提供更加良好的使用体验。通常来说,一个 iOS app 为一个进程,其中又至少有一个线程,即主线程;前者进程由操作系统创建我们很难干预,而线程则「自由」许多,可以为我们所用。
iOS 多线程技术实践之 pthreads(一)
2019.12.14 by kingcosPreface
在现代计算机中,操作系统一般都会支持多进程(Process)以及多线程(Thread)技术,使得其可以同时运行多个程序且效率更高。而我们在开发 iOS app 中也时常需要利用到这些特性,以为用户提供更加良好的使用体验。通常来说,一个 iOS app 为一个进程,其中又至少有一个线程,即主线程;前者进程由操作系统创建我们很难干预,而线程则「自由」许多,可以为我们所用。
由于多线程技术内容较多,我将把相关内容进行拆分,本文作为该系列第一篇,先从 pthreads 说起。
What
POSIX Threads, usually referred to as pthreads, is an execution model that exists independently from a language, as well as a parallel execution model. It allows a program to control multiple different flows of work that overlap in time. Each flow of work is referred to as a thread, and creation and control over these flows is achieved by making calls to the POSIX Threads API.
– POSIX Threads, Wikipedia
译:
POSIX(Portable Operating System Interface of UNIX,可移植操作系统接口)线程,即 pthreads,是一种不依赖于语言的执行模型,也称作并行(Parallel)执行模型。其允许一个程序控制多个时间重叠的不同工作流。每个工作流即为一个线程,通过调用 POSIX 线程 API 创建并控制这些流。
– POSIX 线程,维基百科
如上所述,pthreads 即 POSIX Threads,是一套跨平台的多线程 API,由 C 语言编写。在 Xcode 中,使用 #import <pthread.h>
即可引入 pthreads 相关的 API。但正是由于纯 C 的 API,使用起来不够友好,也需要手动管理线程的整个生命周期。
线程创建
想要开辟一条新的线程来执行任务,我们首先要知道如何创建线程。
PTHREAD_CREATE(3) BSD Library Functions Manual PTHREAD_CREATE(3)
NAME
pthread_create -- create a new thread
SYNOPSIS
#include <pthread.h>
int
pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr, void *(*start_routine)(void *),
void *restrict arg);
DESCRIPTION
The pthread_create() function is used to create a new thread, with
attributes specified by attr, within a process. If attr is NULL, the
default attributes are used. If the attributes specified by attr are
modified later, the thread's attributes are not affected. Upon success-ful successful
ful completion, pthread_create() will store the ID of the created thread
in the location specified by thread.
Upon its creation, the thread executes start_routine, with arg as its
sole argument. If start_routine returns, the effect is as if there was
an implicit call to pthread_exit(), using the return value of
start_routine as the exit status. Note that the thread in which main()
was originally invoked differs from this. When it returns from main(),
the effect is as if there was an implicit call to exit(), using the
return value of main() as the exit status.
The signal state of the new thread is initialized as:
oo The signal mask is inherited from the creating thread.
oo The set of signals pending for the new thread is empty.
RETURN VALUES
If successful, the pthread_create() function will return zero. Other-wise, Otherwise,
wise, an error number will be returned to indicate the error.
ERRORS
pthread_create() will fail if:
[EAGAIN] The system lacked the necessary resources to create
another thread, or the system-imposed limit on the
total number of threads in a process
[PTHREAD_THREADS_MAX] would be exceeded.
[EINVAL] The value specified by attr is invalid.
SEE ALSO
fork(2), pthread_cleanup_pop(3), pthread_cleanup_push(3),
pthread_exit(3), pthread_join(3)
STANDARDS
pthread_create() conforms to ISO/IEC 9945-1:1996 (``POSIX.1'').
BSD April 4, 1996 BSD
pthreads 通过 pthread_create
函数来创建新的线程:
// pthreads.h
/* <rdar://problem/25944576> */
#define _PTHREAD_SWIFT_IMPORTER_NULLABILITY_COMPAT \
defined(SWIFT_CLASS_EXTRA) && (!defined(SWIFT_SDK_OVERLAY_PTHREAD_EPOCH) || (SWIFT_SDK_OVERLAY_PTHREAD_EPOCH < 1))
__API_AVAILABLE(macos(10.4), ios(2.0))
#if !_PTHREAD_SWIFT_IMPORTER_NULLABILITY_COMPAT
int pthread_create(pthread_t _Nullable * _Nonnull __restrict,
const pthread_attr_t * _Nullable __restrict,
void * _Nullable (* _Nonnull)(void * _Nullable),
void * _Nullable __restrict);
#else
int pthread_create(pthread_t * __restrict,
const pthread_attr_t * _Nullable __restrict,
void *(* _Nonnull)(void *), void * _Nullable __restrict);
#endif // _PTHREAD_SWIFT_IMPORTER_NULLABILITY_COMPAT
⚠️ 注意:
可以注意到的是,
pthread.h
中关于pthread_create
函数的声明使用了_PTHREAD_SWIFT_IMPORTER_NULLABILITY_COMPAT
宏进行了区分。关于该宏以及其内部判断条件其实没有太多资料可以参考,但大体上可以理解是为了兼容 Swift 中的可选类型,但两种声明本质上并无其他区别。
以 Obj-C 中实际使用的 pthread_create(pthread_t _Nullable *restrict _Nonnull, const pthread_attr_t *restrict _Nullable, void * _Nullable (* _Nonnull)(void * _Nullable), void *restrict _Nullable)
声明为例,其中 _Nullable
和 _Nonnull
是 Obj-C 桥接 Swift 可选(Optional)类型时是否隐式或显式可选的标志;__restrict
是 C99 标准引入的关键字,类似于 restrict
,可以用在指针声明处,使得编译器将只能通过该指针本身修改指向的内容,便于其优化。去掉这些不影响函数本身功能的标志,补全参数名即 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)
。
pthread_t
pthread_create
中的第一个参数是 pthread_t *thread
,即指向 pthread_t
的指针。pthread_t
的本质是 _opaque_pthread_t
结构体,这里的 opaque
(不透明)意味着外界通常无需关心其内部实现细节:
// sys/_pthread/_pthread_t.h
typedef __darwin_pthread_t pthread_t;
// sys/_pthread/_pthread_types.h
struct _opaque_pthread_t {
long __sig;
struct __darwin_pthread_handler_rec *__cleanup_stack;
char __opaque[__PTHREAD_SIZE__];
};
typedef struct _opaque_pthread_t *__darwin_pthread_t;
我们可以将这个参数理解为线程的引用即可,当 pthread_create
成功创建好线程,传入的 pthread_t
地址即为一个新的线程。
pthread_attr_t
pthread_create
中的第二个参数是 const pthread_attr_t *attr
,即指向 pthread_attr_t
的指针。 pthread_attr_t
的本质是 _opaque_pthread_attr_t
结构体,也是一个不透明类型:
// sys/_pthread/_pthread_attr_t.h
typedef __darwin_pthread_attr_t pthread_attr_t;
// sys/_pthread/_pthread_types.h
struct _opaque_pthread_attr_t {
long __sig;
char __opaque[__PTHREAD_ATTR_SIZE__];
};
typedef struct _opaque_pthread_attr_t __darwin_pthread_attr_t;
pthread_attr_t
指的是线程属性,我们可以使用 pthread_attr_init
函数来初始化,以及使用 pthread_attr_set
开头的函数来进行设置:
// 声明 pthread_attr_t
pthread_attr_t attr;
// 初始化
pthread_attr_init(&attr);
// 设置
pthread_attr_setscope(&attr, int); // 作用域
pthread_attr_setstack(&attr, void * _Nonnull, size_t); // 栈
pthread_attr_setguardsize(&attr, size_t); // 栈末尾的警戒缓冲区大小
pthread_attr_setstackaddr(&attr, void * _Nonnull); // 栈地址
pthread_attr_setstacksize(&attr, size_t); // 栈大小
pthread_attr_setschedparam(&attr, const struct sched_param *restrict _Nonnull); // 调度参数
pthread_attr_setdetachstate(&attr, int); // 分离状态
pthread_attr_setschedpolicy(&attr, int); // 调度策略
pthread_attr_setinheritsched(&attr, int); // 继承
pthread_attr_set_qos_class_np(&attr, qos_class_t __qos_class, int __relative_priority); // 分配 QoS 类
// 创建线程时将 attr 地址传入
// pthread_create(&thread, &attr, ...);
// 使用完毕销毁
pthread_attr_destroy(&attr);
如果我们不太会去自定义这里的属性,可以将 pthread_create
中的该参数传 NULL
即可使用默认属性来创建线程。
pthread_create
pthread_create
中的最后两个参数是 void *(*start_routine) (void *)
和 void *arg
。start_routine()
是新线程运行时所执行的函数,arg
是传入 start_routine()
的唯一参数。当 start_routine()
执行终止或者线程被明确杀死,线程也将会终止;pthread_create
的返回值是 int
类型,当返回 0
时为成功,否则将返回错误码:
#import <Foundation/Foundation.h>
#import <pthread.h>
void runForThread_1(char *arg) {
// pthread_self():返回当前线程 pthread_t
printf("%s (%p) is running.\n", arg, pthread_self());
// 退出线程
pthread_exit(arg);
}
void runForThread_2(void *arg) {
printf("%s (%p) is running.\n", arg, pthread_self());
// 函数返回则自动退出线程
}
int main(int argc, const char * argv[]) {
// 声明两个线程 thread_1 & thread_2
pthread_t thread_1, thread_2;
// 使用默认属性创建 thread_1
int result_1 = pthread_create(&thread_1, NULL, (void *)runForThread_1, "thread_1");
// 打印 thread_1 创建函数返回值 & 线程地址
printf("result_1 - %d - %p\n", result_1, thread_1);
// 检查线程是否创建成功
if (result_1 != 0) {
printf("pthread_create thread_1 error.\n");
}
// 声明线程属性 attr
pthread_attr_t attr;
// 初始化线程属性
pthread_attr_init(&attr);
// 销毁线程属性
pthread_attr_destroy(&attr);
// 使用 attr 创建 thread_2
int result_2 = pthread_create(&thread_2, &attr, (void *)runForThread_2, "thread_2");
printf("result_2 - %d - %p\n", result_2, thread_2);
if (result_2 != 0) {
printf("pthread_create thread_2 error.\n");
}
// 人为休息 1 秒
sleep(1);
return 0;
}
// OUTPUT:
// result_1 - 0 - 0x70000799e000
// result_2 - 22 - 0x0
// pthread_create thread_2 error.
// thread_1 (0x70000799e000) is running.
这里我们使用默认属性 NULL
即可创建通用化的线程;而如果使用已经销毁的线程属性 pthread_attr_t
来创建线程时,就会出现错误从而无法成功创建线程。
需要注意的一点是,pthread_create
函数的第四个参数声明的类型是 void *
,即可以指向任意类型的指针。但当我们直接传入 int
等类型的变量时,虽然执行没有问题,但编译器将总是有类似这样的警告 Incompatible integer to pointer conversion passing 'int' to parameter of type 'void *'
:
#import <Foundation/Foundation.h>
#import <pthread.h>
#define N 5
void *thread_func(void *arg) {
// WARNING: Format specifies type 'int' but the argument has type 'void *'
printf("arg = %d\n", arg);
return NULL;
}
int main(int argc, const char * argv[]) {
int i;
pthread_t threads[N];
for (i = 0; i < N; i++) {
// WARNING: Incompatible integer to pointer conversion passing 'int' to parameter of type 'void *'
pthread_create(&threads[i], NULL, thread_func, i);
}
// 人为休息 1 秒
sleep(1);
return 0;
}
// OUTPUT:
// arg = 1
// arg = 2
// arg = 3
// arg = 4
// arg = 0
首先可以想到的是强制类型转换,即 (void *)i
即可将 int
类型的 i
转换为 void *
来满足需要。但由于 int
类型只占用 4 个字节,小于 void *
指针占用的 8 个字节,因此这里又有了新的警告 Cast to 'void *' from smaller integer type 'int'
;另外一个可以想到的方法是将变量的地址即 &i
传入,这正好符合此处对于类型的要求,因此警告可以消除,线程函数内部使用时也需要将参数进行取值操作:
#import <Foundation/Foundation.h>
#import <pthread.h>
#define N 5
void *thread_func(void *arg) {
int *ptr = arg;
printf("arg = %d\n", *ptr);
return NULL;
}
int main(int argc, const char * argv[]) {
int i;
pthread_t threads[N];
for (i = 0; i < N; i++) {
pthread_create(&threads[i], NULL, thread_func, &i);
}
// 人为休息 1 秒
sleep(1);
return 0;
}
// OUTPUT:
// arg = 3
// arg = 0
// arg = 4
// arg = 0
// arg = 4
但我们发现最后的输出似乎有些重复,这是因为传入地址是有隐患的,按地址传入会使得外界更改变量值时,线程内部使用的参数也会被同时改变。
那么如何仍然使用按值传递又可以避免警告呢?其实只需要将 int
类型先转为 8 个字节长度的 long
或 intptr_t
类型,再转为 void *
即可:
#import <Foundation/Foundation.h>
#import <pthread.h>
#define N 5
void *thread_func(void *arg) {
printf("arg = %ld\n", (intptr_t)arg);
return NULL;
}
int main(int argc, const char * argv[]) {
int i;
pthread_t threads[N];
for (i = 0; i < N; i++) {
pthread_create(&threads[i], NULL, thread_func, (void *)(intptr_t)i);
}
// 人为休息 1 秒
sleep(1);
return 0;
}
// OUTPUT:
// arg = 0
// arg = 3
// arg = 1
// arg = 2
// arg = 4
pthread_create in Swift
如上文所述,pthreads API 在 Swift 中也可以使用。我们可以在 Swift 中 pthread_create
函数声明处发现,其四个参数均与 Unsafe...Pointer
相关的类型有关,这样的命名是因为 Swift 中的指针语法也被抽象为类型而非 *
,并通过 Unsafe
告知开发者其中可能存在隐患。而这里由于 pthread_create
本质是一个 C 语言函数,但 Swift 又无法直接与 C/C++ 进行混编,因此需要做一些转换:
C 语法 | Swift 语法 |
---|---|
const Type * |
UnsafePointer<Type> |
Type * |
UnsafeMutablePointer<Type> |
Type * const * |
UnsafePointer<Type> |
Type * __strong * |
UnsafeMutablePointer<Type> |
Type ** |
AutoreleasingUnsafeMutablePointer<Type> |
const void * |
UnsafeRawPointer |
void * |
UnsafeMutableRawPointer |
pthread_create
函数中,第一个参数 pthread_t *thread
在 Swift 中将被表示为 UnsafeMutablePointer<pthread_t>
,第二个参数 const pthread_attr_t *attr
将被表示为 UnsafePointer<pthread_attr_t>
,第三个参数 void *(*start_routine) (void *)
被表示为 (UnsafeMutableRawPointer) -> (UnsafeMutableRawPointer)
,以及最后一个参数 void *arg
将被表示为 UnsafeMutableRawPointer
,这些参数再加上可空性对应的可选类型,最终形成了 Swift 中 pthread_create
函数的 API:
@available(iOS 2.0, *)
public func pthread_create(_: UnsafeMutablePointer<pthread_t?>!, _: UnsafePointer<pthread_attr_t>?, _: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?) -> Int32
// _PTHREAD_SWIFT_IMPORTER_NULLABILITY_COMPAT
前两个参数我们在 Swift 中仍然可以使用 &
来按地址传递,那么后两个参数 C 语言函数和参数该如何传递呢?
为了兼容 Swift 中的各种类型,我们可以将值封装在一个支持泛型的 Box
类型中;并额外定义两个方法,一是 encode
:将 Swift 中的值封装在 UnsafeMutableRawPointer
指针类型中,二是 decode
:将 UnsafeMutableRawPointer
指针类型中真正的值取出。这两个方法需要成对使用:
class Box<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
private func decode<T>(_ memory: UnsafeMutableRawPointer) -> T {
let unmanaged = Unmanaged<Box<T>>.fromOpaque(memory)
defer { unmanaged.release() } // 延迟释放
return unmanaged.takeUnretainedValue().value
}
private func encode<T>(_ t: T) -> UnsafeMutableRawPointer {
return Unmanaged.passRetained(Box(t)).toOpaque()
}
接下来的使用就十分类似 Obj-C 了:
// 需声明 Swift 类外
func runForThread_1(_ arg: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? {
print("\(decode(arg) as String) (\(pthread_self())) is running.")
return nil
}
// 声明两个线程 thread_1 & thread_2
var thread_1, thread_2: pthread_t!
// 创建 thread_1
let result_1 = pthread_create(&thread_1, nil, runForThread_1, encode("thread_1"))
print("result_1 - \(result_1) - \(String(describing: thread_1))");
// 检查线程是否创建成功
if result_1 != 0 {
print("pthread_create thread_1 error.")
}
// 声明线程属性 attr,注意这里需要使用 pthread_attr_t 构造
var attr = pthread_attr_t()
// 初始化线程属性
pthread_attr_init(&attr)
// 销毁线程属性
pthread_attr_destroy(&attr)
let result_2 = pthread_create(&thread_2, &attr, runForThread_1, encode("thread_1"))
print("result_2 - \(result_2) - \(String(describing: thread_2))");
if result_2 != 0 {
print("pthread_create thread_2 error.")
}
// 人为休息 1 秒
sleep(1)
// OUTPUT:
// result_1 - 0 - Optional(0x0000700002cd8000)
// thread_1 (0x0000700002cd8000) is running.
// result_2 - 22 - nil
// pthread_create thread_2 error.
线程执行
在「线程创建」一节中,我们可以发现 pthread_create
函数不仅创建了新的线程,其也会执行线程函数,而不像某些 API 的创建与执行是分开的。
其实当 pthread_create
函数返回 0
时,它只能保证线程被成功创建,但并不能保证线程可以得到立即执行。只有当新的线程获得了 CPU 的时间片(Timeslice),其才可以得到执行。而是否能够优先获得时间片,则取决于线程的优先级。线程默认属性的调度策略是 SCHED_OTHER
,该策略下没有优先级选择,因此我们可以通过 pthread_attr_setschedpolicy
更改调度策略:
// pthread/pthread_impl.h
/*
* POSIX scheduling policies
*/
#define SCHED_OTHER 1
#define SCHED_FIFO 4
#define SCHED_RR 2
// main.m
pthread_attr_setschedpolicy(&attr, SCHED_RR);
线程同步
pthread_join
在上面代码示例中的 main
函数中,都在末尾会有这么一句 sleep(1)
,作用是让当前线程休息 1 秒,这是为什么呢?我们可以尝试注释掉这一行 sleep()
函数的调用再运行一次:
#import <Foundation/Foundation.h>
#import <pthread.h>
void runForThread(char *arg) {
printf("%s (%p) is running.\n", arg, pthread_self());
}
int main(int argc, const char * argv[]) {
// 输出主线程信息
printf("(%p) is running.\n", pthread_self());
pthread_t thread;
if (pthread_create(&thread, NULL, (void *)runForThread, "thread") != 0) {
printf("pthread_create thread error.\n");
}
// sleep(1);
return 0;
}
// OUTPUT:
// (0x1000d2dc0) is running.
此时将只有主线程的输出,而新创建的子线程 thread
却没有输出。这是因为主线程执行结束即程序结束, thread
子线程根本来不及去执行。所以 sleep(1)
目的是人为将主线程休眠 1 秒,等待子线程去执行,但这样硬核去休眠固定的时间并不是合理的操作。其实 pthreads 为我们提供了 pthread_join
API 来阻塞当前线程,等待目标线程执行完毕后再继续执行当前线程:
PTHREAD_JOIN(3) BSD Library Functions Manual PTHREAD_JOIN(3)
NAME
pthread_join -- wait for thread termination
SYNOPSIS
#include <pthread.h>
int
pthread_join(pthread_t thread, void **value_ptr);
DESCRIPTION
The pthread_join() function suspends execution of the calling thread
until the target thread terminates, unless the target thread has already
terminated.
On return from a successful pthread_join() call with a non-NULL value_ptr
argument, the value passed to pthread_exit() by the terminating thread is
stored in the location referenced by value_ptr. When a pthread_join()
returns successfully, the target thread has been terminated. The results
of multiple simultaneous calls to pthread_join(), specifying the same
target thread, are undefined. If the thread calling pthread_join() is
cancelled, the target thread is not detached.
RETURN VALUES
If successful, the pthread_join() function will return zero. Otherwise,
an error number will be returned to indicate the error.
ERRORS
pthread_join() will fail if:
[EDEADLK] A deadlock was detected or the value of thread speci-fies specifies
fies the calling thread.
[EINVAL] The implementation has detected that the value speci-fied specified
fied by thread does not refer to a joinable thread.
[ESRCH] No thread could be found corresponding to that speci-fied specified
fied by the given thread ID, thread.
SEE ALSO
wait(2), pthread_create(3)
STANDARDS
pthread_join() conforms to ISO/IEC 9945-1:1996 (``POSIX.1'').
BSD April 4, 1996 BSD
pthread_join
函数在 Obj-C 下的声明如下,其中,第一个参数即等待的目标线程;第二个参数保存了线程函数的返回值;返回值是 int
类型,当返回 0
时为成功,否则将返回错误码:
// pthread.h
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_join(pthread_t , void * _Nullable * _Nullable)
__DARWIN_ALIAS_C(pthread_join);
我们尝试使用 pthread_join
改写上面的代码示例:
#import <Foundation/Foundation.h>
#import <pthread.h>
void runForThread(char *arg) {
printf("%s (%p) is running.\n", arg, pthread_self());
}
int main(int argc, const char * argv[]) {
printf("(%p) is running.\n", pthread_self());
pthread_t thread;
if (pthread_create(&thread, NULL, (void *)runForThread, "thread") != 0) {
printf("pthread_create thread error.\n");
}
pthread_join(thread, NULL);
printf("main thread exit.\n");
return 0;
}
// OUTPUT:
// (0x1000d2dc0) is running.
// thread (0x70000289c000) is running.
// main thread exit.
这次主线程将一直等待 thread
子线程执行结束后才继续执行,这种两个或多个线程协作执行,可以称之为线程同步。当然,pthread_join
只是线程同步的一种方式。
pthread_join
中仍需注意的一点是,其第二个参数类型是 void **
,因此传入的应当是 void *
变量的地址:
#import <Foundation/Foundation.h>
#import <pthread.h>
void *runForThread(char *arg) {
printf("%s (%p) is running.\n", arg, pthread_self());
// 使用 malloc 分配内存
double *result = (double *)malloc(sizeof(double));
*result = 3.14;
// 将结果作为线程退出函数的参数,或直接返回结果
// pthread_exit((void *)result);
return (void *)result;
}
int main(int argc, const char * argv[]) {
printf("(%p) is running.\n", pthread_self());
pthread_t thread;
if (pthread_create(&thread, NULL, (void *)runForThread, "thread") != 0) {
printf("pthread_create thread error.\n");
}
void *result = NULL;
pthread_join(thread, &result);
printf("thread result - %lf\n", *(double *)result);
// 使用 free 释放内存
free(result);
printf("main thread exit.\n");
return 0;
}
// OUTPUT:
// (0x1000d2dc0) is running.
// thread (0x70000537e000) is running.
// thread result - 3.140000
// main thread exit.
Swift 下的 pthread_join
用法基本一致:
func runForThread(_ arg: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? {
print("\(decode(arg) as String) (\(pthread_self())) is running.")
return encode(3.14)
}
print("(\(pthread_self())) is running.")
var thread: pthread_t!
if pthread_create(&thread, nil, runForThread, encode("thread")) != 0 {
print("pthread_create thread error.")
}
var res: UnsafeMutableRawPointer!
pthread_join(thread, &res)
print("res - \(decode(res) as Double)")
print("main thread exit.")
// OUTPUT:
// (0x00000001000d6dc0) is running.
// thread (0x0000700007bb2000) is running.
// res - 3.14
// main thread exit.
互斥锁
pthreads 中还提供了互斥锁(Mutex)来实现线程同步,但由于锁相关的内容将会统一整理,本文不再赘述。
条件变量
由于条件变量内容与互斥锁有所关联,一并统一整理,本文不再赘述。
Reference
- POSIX Threads - Wikipedia
- restrict - Wikipedia
- [译] 在 Objective-C API 中指定可空性 - kingcos
- sem_init on OS X - StackOverflow
- ZewoGraveyard/POSIX - GitHub
- pthread - iOS Manual Pages
- Thread ID vs. Pthread Handle (pthread_t) - IBM
- Using Imported C Functions in Swift - Apple
- How to cast an integer to void pointer? - StackOverflow
- Energy Efficiency Guide for iOS Apps - Apple
- POSIX thread (pthread) libraries - CMU
- Does pthread_create start the created thread? - StackOverflow
- How to increase thread priority in pthreads? - StackOverflow