pthread
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
(可移植操作系统接口)线程,即pthreads
,是一种不依赖于语言的执行模型,也称作并行(Parallel)执行模型。其允许一个程序控制多个时间重叠的不同工作流。每个工作流即为一个线程,通过调用 POSIX 线程 API 创建并控制这些流。
pthread
全称POSIX thread
,是一套跨平台的多线程API,各个平台对其都有实现。pthread
是一套非常强大的多线程锁,可以创建互斥锁(普通锁)、递归锁、信号量、条件锁、读写锁、once
锁等,基本上所有涉及的锁,都可以使用pthread
来实现。
创建线程
int pthread_create(pthread_t _Nullable * _Nonnull __restrict,
const pthread_attr_t * _Nullable __restrict,
void * _Nullable (* _Nonnull)(void * _Nullable),
void * _Nullable __restrict);
_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 *thread
。关于该参数实际意义,一种指整型(Integer)四字节的线程 ID,另一种指包含值和其他的抽象结构体,这种抽象有助于在一个进程(Process)中实现扩展到数千个线程,后者也可以称为 pthread
的句柄(Handle)。
在 Obj-C
中,pthread_t
属于后者,其本质是 _opaque_pthread_t
,一个不透明类型(Opaque Type)的结构体,即一种外界只需要知道其存在,而无需关心内部实现细节的数据类型。在函数中,该参数是作为指针传入的,总之我们可以简单将这个参数理解为线程的引用即可,通过它能找到线程的 ID 或者其他信息。
typedef __darwin_pthread_t pthread_t;
typedef struct _opaque_pthread_t *__darwin_pthread_t;
struct _opaque_pthread_t {
long __sig;
struct __darwin_pthread_handler_rec *__cleanup_stack;
char __opaque[__PTHREAD_SIZE__];
};
第二个参数是 const pthread_attr_t *attr
。pthread_attr_t
本质也是一个不透明类型的结构体。可以使用 int pthread_attr_init(pthread_attr_t *)
; 初始化该结构体。该参数为 NULL
时将使用默认属性来创建线程。
typedef __darwin_pthread_attr_t pthread_attr_t;
typedef struct _opaque_pthread_attr_t __darwin_pthread_attr_t;
struct _opaque_pthread_attr_t {
long __sig;
char __opaque[__PTHREAD_ATTR_SIZE__];
};
最后两个参数是 void *(*start_routine) (void *)
, void *arg
。start_routine()
是新线程运行时所执行的函数,arg
是传入 start_routine()
的参数。当 start_routine()
执行终止或者线程被明确杀死,线程也将会终止。
返回值是int
类型,当返回0
时,创建成功,否则将返回错误码。
简单使用
#import "pthread.h"
void * runForPthreadCreate(void * arg) {
// 打印参数 & 当前线程 __opaque 属性的地址
printf("%s (%p) is running.\n", arg, &pthread_self()->__opaque);
exit(2);
}
- (void)pthreadCreate {
// 声明 thread_1 & thread_2
pthread_t thread_1, thread_2;
// 创建 thread_1
int result_1 = pthread_create(&thread_1, NULL, runForPthreadCreate, "thread_1");
// 打印 thread_1 创建函数返回值 & __opaque 属性的地址
printf("result_1 - %d - %p\n", result_1, &thread_1->__opaque);
// 检查线程是否创建成功
if (result_1 != 0) {
perror("pthread_create thread_1 error.");
// 线程创建失败退出码为 1
exit(1);
}
// 创建 thread_2
int result_2 = pthread_create(&thread_2, NULL, runForPthreadCreate, "thread_2");
// 打印 thread_2 创建函数返回值 & __opaque 属性的地址
printf("result_2 - %d - %p\n", result_2, &thread_2->__opaque);
if (result_2 != 0) {
perror("pthread_create thread_2 error.");
// 线程创建失败退出码为 1
exit(1);
}
// sleep(1);
// 主线程退出码为 3
exit(3);
}
- 互斥锁(pthread_mutex)
pthread_mutex
表示互斥锁。互斥锁的实现原理与信号量非常相似,不是使用忙等,而是阻塞线程并睡眠,需要进行上下文切换。pthread_mutex
是 C 语言下多线程加互斥锁的方式
typedef __darwin_pthread_mutex_t pthread_mutex_t;
typedef struct _opaque_pthread_mutex_t __darwin_pthread_mutex_t;
struct _opaque_pthread_mutex_t {
long __sig;
char __opaque[__PTHREAD_MUTEX_SIZE__];
};
常用函数
// 初始化锁变量mutex。attr为锁属性,NULL值为默认属性。
int pthread_mutex_init(pthread_mutex_t * __restrict,
const pthread_mutexattr_t * __restrict);
// 加锁
int pthread_mutex_lock(pthread_mutex_t *);
// 加锁,但是与2不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待。
int pthread_mutex_trylock(pthread_mutex_t *);
// 解锁
int pthread_mutex_unlock(pthread_mutex_t *);
// 使用完后释放
pthread_mutex_destroy(pthread_mutex_t* *mutex);
简单使用
#import <pthread.h>
static pthread_mutex_t theLock;
-(void)pthread {
pthread_mutex_init(&theLock, NULL);
pthread_t thread1;
pthread_create(&thread1, NULL, threadMethod1, NULL);
pthread_t thread2;
pthread_create(&thread2, NULL, threadMethod2, NULL);
}
void *threadMethod1() {
pthread_mutex_lock(&theLock);
printf("线程1\n");
sleep(2);
pthread_mutex_unlock(&theLock);
printf("线程1解锁成功\n");
return 0;
}
void *threadMethod2() {
sleep(1);
pthread_mutex_lock(&theLock);
printf("线程2\n");
pthread_mutex_unlock(&theLock);
return 0;
}
运行输出
线程1
线程1解锁成功
线程2
一般情况下,一个线程只能申请一次锁,也只能在获得锁的情况下才能释放锁,多次申请锁或释放未获得的锁都会导致崩溃。假设在已经获得锁的情况下再次申请锁,线程会因为等待锁的释放而进入睡眠状态,因此就不可能再释放锁,从而导致死锁。
然而这种情况经常会发生,比如某个函数申请了锁,在临界区内又递归调用了自己。辛运的是 pthread_mutex
支持递归锁,也就是允许一个线程递归的申请锁,只要把 attr
的类型改成 PTHREAD_MUTEX_RECURSIVE
即可。
- 递归锁(pthread_mutexattr_t)
Mutex
可以分为递归锁(recursive mutex)和非递归锁(non-recursive mutex)。可递归锁也可称为可重入锁(reentrant mutex),非递归锁又叫不可重入锁(non-reentrant mutex)。
二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。
typedef __darwin_pthread_mutexattr_t pthread_mutexattr_t;
typedef struct _opaque_pthread_mutexattr_t __darwin_pthread_mutexattr_t;
struct _opaque_pthread_mutexattr_t {
long __sig;
char __opaque[__PTHREAD_MUTEXATTR_SIZE__];
};
递归锁的创建方法跟普通锁是同一个方法,不过需要传递一个attr
参数.
锁的类型
/*
* Mutex type attributes
*/
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
1、PTHREAD_MUTEX_NORMAL
缺省类型,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。
2、PTHREAD_MUTEX_ERRORCHECK
检错锁,如果同一个线程请求同一个锁,则返回EDEADLK
,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。
3、PTHREAD_MUTEX_RECURSIVE
递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。
4、PTHREAD_MUTEX_DEFAULT
互斥锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。
简单使用
-(void)pthread_mutexattr {
__block pthread_mutex_t theLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 设置为递归锁
pthread_mutex_init(&theLock, &attr); // 创建锁
pthread_mutexattr_destroy(&attr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value) {
pthread_mutex_lock(&theLock);
if (value > 0) {
NSLog(@"value = %d", value);
sleep(1);
RecursiveMethod(value - 1);
}
pthread_mutex_unlock(&theLock);
};
RecursiveMethod(5);
});
}
运行输出
value = 5
value = 4
value = 3
value = 2
value = 1
这是pthread_mutex
为了防止在递归的情况下出现死锁而出现的递归锁。作用和NSRecursiveLock
递归锁类似
- 条件锁(pthread_cond_t)
typedef __darwin_pthread_cond_t pthread_cond_t;
typedef struct _opaque_pthread_cond_t __darwin_pthread_cond_t;
struct _opaque_pthread_cond_t {
long __sig;
char __opaque[__PTHREAD_COND_SIZE__];
};
常用函数
// 等待条件锁
int pthread_cond_wait(pthread_cond_t * __restrict,
pthread_mutex_t * __restrict);
// 激动一个相同条件的条件锁
int pthread_cond_signal(pthread_cond_t *);
简单使用
- (void)conditionLock {
// 初始化锁变量mutex
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
// 条件锁
__block pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
// 线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 加锁
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
NSLog(@"thread 1");
// 解锁
pthread_mutex_unlock(&mutex);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
NSLog(@"thread 2");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
});
}
运行结果
thread 2
thread 1
- 读写锁(pthread_rwlock_t)
读写锁是一种特殊的自旋锁,将对资源的访问分为读者和写者,顾名思义,读者对资源只进行读取,而写者对资源只有写访问,相对于自旋锁来说,这中锁能提高并发性。在多核处理器操作系统中,允许多个读者访问同一资源,却只能有一个写者执行写操作,并且读写操作不能同时进行
typedef __darwin_pthread_rwlock_t pthread_rwlock_t;
typedef struct _opaque_pthread_rwlock_t __darwin_pthread_rwlock_t;
struct _opaque_pthread_rwlock_t {
long __sig;
char __opaque[__PTHREAD_RWLOCK_SIZE__];
};
常用函数
// 初始化都写锁
int pthread_rwlock_init(pthread_rwlock_t * __restrict,
const pthread_rwlockattr_t * _Nullable __restrict);
// 读操作上锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *);
// 写操作上锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *);
// 获取写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *);
// 获取读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *);
// 解锁
int pthread_rwlock_unlock(pthread_rwlock_t *);
// 释放锁
int pthread_rwlock_destroy(pthread_rwlock_t * );
简单使用
static pthread_rwlock_t rwlock;
// 读写锁
- (void)rwlock {
pthread_rwlock_init(&rwlock, NULL);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readBookWithTag:0];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readBookWithTag:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self writeBook:2];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readBookWithTag:3];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self writeBook:4];
});
}
- (void)readBookWithTag:(NSInteger )tag {
pthread_rwlock_rdlock(&rwlock);
NSLog(@"读%tu",tag);
sleep(3);
pthread_rwlock_unlock(&rwlock);
}
- (void)writeBook:(NSInteger)tag {
pthread_rwlock_wrlock(&rwlock);
NSLog(@"写%tu",tag);
sleep(3);
pthread_rwlock_unlock(&rwlock);
}
运行输出
读0
读1
写2
读3
读4
- once锁(pthread_once)
一次性初始化pthread_once
static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_mutex_t mutex;
void init() {
pthread_mutex_init(&mutex, NULL);
}
void performWork() {
pthread_once(&once, init); // Correct
pthread_mutex_lock(&mutex);
// ...
pthread_mutex_unlock(&mutex);
}
- pthread(semaphore)
pthread的信号量不同于GCD自带的信号量,如前面所说,pthread是一套跨平台的多线程API,对信号量也提供了相应的使用。其大概原理和使用方法与GCD提供的信号量机制类似,使用起来也比较方便。
信号量机制通过信号量的值控制可用资源的数量。线程访问共享资源前,需要申请获取一个信号量,如果信号量为0,说明当前无可用的资源,线程无法获取信号量,则该线程会等待其他资源释放信号量(信号量加1)。如果信号量不为0,说明当前有可用的资源,此时线程占用一个资源,对应信号量减1。
// 该函数申请一个信号量,当前无可用信号量则等待,有可用信号量时占用一个信号量,对信号量的值减1。
int sem_wait(sem_t *sem);
// 该函数释放一个信号量,信号量的值加1。
int sem_post(sem_t *sem);
简单使用
#import "pthread.h"
#import <sys/semaphore.h>
static sem_t *semaphore;
void *runForSemaphoreDemo1(void * arg) {
// 等待信号
sem_wait(semaphore);
printf("Running - %s.\n", arg);
return 0;
}
void *runForSemaphoreDemo2(void * arg) {
printf("Running - %s.\n", arg);
sleep(1);
// 发送信号
sem_post(semaphore);
return 0;
}
- (void)semaphoreDemo {
// sem_init 初始化匿名信号量在 macOS 中已被废弃
// semaphore = sem_init(&semaphore, 0, 0);
semaphore = sem_open("sem", 0, 0);
pthread_t thread_1, thread_2;
void * result;
if (pthread_create(&thread_1, NULL, runForSemaphoreDemo1, "Thread 1") != 0) {
perror("pthread_create thread_1 error.");
exit(1);
}
if (pthread_create(&thread_2, NULL, runForSemaphoreDemo2, "Thread 2") != 0) {
perror("pthread_create thread_2 error.");
exit(1);
}
// thread_join用来等待一个线程的结束
pthread_join(thread_1, &result);
pthread_join(thread_2, &result);
}
运行输出
Running - Thread 2.
Running - Thread 1.
网友评论