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(可移植操作系統(tǒng)接口)線程,即pthreads,是一種不依賴于語言的執(zhí)行模型,也稱作并行(Parallel)執(zhí)行模型。其允許一個(gè)程序控制多個(gè)時(shí)間重疊的不同工作流。每個(gè)工作流即為一個(gè)線程,通過調(diào)用 POSIX 線程 API 創(chuàng)建并控制這些流。
pthread全稱POSIX thread,是一套跨平臺(tái)的多線程API,各個(gè)平臺(tái)對(duì)其都有實(shí)現(xiàn)。pthread是一套非常強(qiáng)大的多線程鎖,可以創(chuàng)建互斥鎖(普通鎖)、遞歸鎖、信號(hào)量、條件鎖、讀寫鎖、once鎖等,基本上所有涉及的鎖,都可以使用pthread來實(shí)現(xiàn)。
創(chuàng)建線程
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)類型的標(biāo)志;__restrict 是 C99 標(biāo)準(zhǔn)引入的關(guān)鍵字,類似于restrict,可以用在指針聲明處,用于告訴編譯器只有該指針本身才能修改指向的內(nèi)容,便于編譯器優(yōu)化。
去掉這些不影響函數(shù)本身的標(biāo)志,補(bǔ)全參數(shù)名,即
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
函數(shù)的四個(gè)參數(shù)
其中第一個(gè)參數(shù)是 pthread_t *thread。關(guān)于該參數(shù)實(shí)際意義,一種指整型(Integer)四字節(jié)的線程 ID,另一種指包含值和其他的抽象結(jié)構(gòu)體,這種抽象有助于在一個(gè)進(jìn)程(Process)中實(shí)現(xiàn)擴(kuò)展到數(shù)千個(gè)線程,后者也可以稱為 pthread的句柄(Handle)。
在 Obj-C 中,pthread_t 屬于后者,其本質(zhì)是 _opaque_pthread_t,一個(gè)不透明類型(Opaque Type)的結(jié)構(gòu)體,即一種外界只需要知道其存在,而無需關(guān)心內(nèi)部實(shí)現(xiàn)細(xì)節(jié)的數(shù)據(jù)類型。在函數(shù)中,該參數(shù)是作為指針傳入的,總之我們可以簡單將這個(gè)參數(shù)理解為線程的引用即可,通過它能找到線程的 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__];
};
第二個(gè)參數(shù)是 const pthread_attr_t *attr。pthread_attr_t 本質(zhì)也是一個(gè)不透明類型的結(jié)構(gòu)體??梢允褂?int pthread_attr_init(pthread_attr_t *); 初始化該結(jié)構(gòu)體。該參數(shù)為 NULL 時(shí)將使用默認(rèn)屬性來創(chuàng)建線程。
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__];
};
最后兩個(gè)參數(shù)是 void *(*start_routine) (void *), void *arg。start_routine() 是新線程運(yùn)行時(shí)所執(zhí)行的函數(shù),arg 是傳入 start_routine() 的參數(shù)。當(dāng) start_routine() 執(zhí)行終止或者線程被明確殺死,線程也將會(huì)終止。
返回值是int 類型,當(dāng)返回0時(shí),創(chuàng)建成功,否則將返回錯(cuò)誤碼。
簡單使用
#import "pthread.h"
void * runForPthreadCreate(void * arg) {
// 打印參數(shù) & 當(dāng)前線程 __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;
// 創(chuàng)建 thread_1
int result_1 = pthread_create(&thread_1, NULL, runForPthreadCreate, "thread_1");
// 打印 thread_1 創(chuàng)建函數(shù)返回值 & __opaque 屬性的地址
printf("result_1 - %d - %p\n", result_1, &thread_1->__opaque);
// 檢查線程是否創(chuàng)建成功
if (result_1 != 0) {
perror("pthread_create thread_1 error.");
// 線程創(chuàng)建失敗退出碼為 1
exit(1);
}
// 創(chuàng)建 thread_2
int result_2 = pthread_create(&thread_2, NULL, runForPthreadCreate, "thread_2");
// 打印 thread_2 創(chuàng)建函數(shù)返回值 & __opaque 屬性的地址
printf("result_2 - %d - %p\n", result_2, &thread_2->__opaque);
if (result_2 != 0) {
perror("pthread_create thread_2 error.");
// 線程創(chuàng)建失敗退出碼為 1
exit(1);
}
// sleep(1);
// 主線程退出碼為 3
exit(3);
}
- 互斥鎖(pthread_mutex)
pthread_mutex表示互斥鎖?;コ怄i的實(shí)現(xiàn)原理與信號(hào)量非常相似,不是使用忙等,而是阻塞線程并睡眠,需要進(jìn)行上下文切換。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__];
};
常用函數(shù)
// 初始化鎖變量mutex。attr為鎖屬性,NULL值為默認(rèn)屬性。
int pthread_mutex_init(pthread_mutex_t * __restrict,
const pthread_mutexattr_t * __restrict);
// 加鎖
int pthread_mutex_lock(pthread_mutex_t *);
// 加鎖,但是與2不一樣的是當(dāng)鎖已經(jīng)在使用的時(shí)候,返回為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;
}
運(yùn)行輸出
線程1
線程1解鎖成功
線程2
一般情況下,一個(gè)線程只能申請(qǐng)一次鎖,也只能在獲得鎖的情況下才能釋放鎖,多次申請(qǐng)鎖或釋放未獲得的鎖都會(huì)導(dǎo)致崩潰。假設(shè)在已經(jīng)獲得鎖的情況下再次申請(qǐng)鎖,線程會(huì)因?yàn)榈却i的釋放而進(jìn)入睡眠狀態(tài),因此就不可能再釋放鎖,從而導(dǎo)致死鎖。
然而這種情況經(jīng)常會(huì)發(fā)生,比如某個(gè)函數(shù)申請(qǐng)了鎖,在臨界區(qū)內(nèi)又遞歸調(diào)用了自己。辛運(yùn)的是 pthread_mutex 支持遞歸鎖,也就是允許一個(gè)線程遞歸的申請(qǐng)鎖,只要把 attr 的類型改成 PTHREAD_MUTEX_RECURSIVE 即可。
- 遞歸鎖(pthread_mutexattr_t)
Mutex可以分為遞歸鎖(recursive mutex)和非遞歸鎖(non-recursive mutex)??蛇f歸鎖也可稱為可重入鎖(reentrant mutex),非遞歸鎖又叫不可重入鎖(non-reentrant mutex)。
二者唯一的區(qū)別是,同一個(gè)線程可以多次獲取同一個(gè)遞歸鎖,不會(huì)產(chǎn)生死鎖。而如果一個(gè)線程多次獲取同一個(gè)非遞歸鎖,則會(huì)產(chǎn)生死鎖。
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__];
};
遞歸鎖的創(chuàng)建方法跟普通鎖是同一個(gè)方法,不過需要傳遞一個(gè)attr參數(shù).
鎖的類型
/*
* 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
缺省類型,也就是普通鎖。當(dāng)一個(gè)線程加鎖以后,其余請(qǐng)求鎖的線程將形成一個(gè)等待隊(duì)列,并在解鎖后先進(jìn)先出原則獲得鎖。
2、PTHREAD_MUTEX_ERRORCHECK
檢錯(cuò)鎖,如果同一個(gè)線程請(qǐng)求同一個(gè)鎖,則返回EDEADLK,否則與普通鎖類型動(dòng)作相同。這樣就保證當(dāng)不允許多次加鎖時(shí)不會(huì)出現(xiàn)嵌套情況下的死鎖。
3、PTHREAD_MUTEX_RECURSIVE
遞歸鎖,允許同一個(gè)線程對(duì)同一個(gè)鎖成功獲得多次,并通過多次 unlock 解鎖。
4、PTHREAD_MUTEX_DEFAULT
互斥鎖,動(dòng)作最簡單的鎖類型,僅等待解鎖后重新競爭,沒有等待隊(duì)列。
簡單使用
-(void)pthread_mutexattr {
__block pthread_mutex_t theLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 設(shè)置為遞歸鎖
pthread_mutex_init(&theLock, &attr); // 創(chuàng)建鎖
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);
});
}
運(yùn)行輸出
value = 5
value = 4
value = 3
value = 2
value = 1
這是pthread_mutex為了防止在遞歸的情況下出現(xiàn)死鎖而出現(xiàn)的遞歸鎖。作用和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__];
};
常用函數(shù)
// 等待條件鎖
int pthread_cond_wait(pthread_cond_t * __restrict,
pthread_mutex_t * __restrict);
// 激動(dòng)一個(gè)相同條件的條件鎖
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);
});
}
運(yùn)行結(jié)果
thread 2
thread 1
- 讀寫鎖(pthread_rwlock_t)
讀寫鎖是一種特殊的自旋鎖,將對(duì)資源的訪問分為讀者和寫者,顧名思義,讀者對(duì)資源只進(jìn)行讀取,而寫者對(duì)資源只有寫訪問,相對(duì)于自旋鎖來說,這中鎖能提高并發(fā)性。在多核處理器操作系統(tǒng)中,允許多個(gè)讀者訪問同一資源,卻只能有一個(gè)寫者執(zhí)行寫操作,并且讀寫操作不能同時(shí)進(jìn)行
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__];
};
常用函數(shù)
// 初始化都寫鎖
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);
}
運(yùn)行輸出
讀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的信號(hào)量不同于GCD自帶的信號(hào)量,如前面所說,pthread是一套跨平臺(tái)的多線程API,對(duì)信號(hào)量也提供了相應(yīng)的使用。其大概原理和使用方法與GCD提供的信號(hào)量機(jī)制類似,使用起來也比較方便。
信號(hào)量機(jī)制通過信號(hào)量的值控制可用資源的數(shù)量。線程訪問共享資源前,需要申請(qǐng)獲取一個(gè)信號(hào)量,如果信號(hào)量為0,說明當(dāng)前無可用的資源,線程無法獲取信號(hào)量,則該線程會(huì)等待其他資源釋放信號(hào)量(信號(hào)量加1)。如果信號(hào)量不為0,說明當(dāng)前有可用的資源,此時(shí)線程占用一個(gè)資源,對(duì)應(yīng)信號(hào)量減1。
// 該函數(shù)申請(qǐng)一個(gè)信號(hào)量,當(dāng)前無可用信號(hào)量則等待,有可用信號(hào)量時(shí)占用一個(gè)信號(hào)量,對(duì)信號(hào)量的值減1。
int sem_wait(sem_t *sem);
// 該函數(shù)釋放一個(gè)信號(hào)量,信號(hào)量的值加1。
int sem_post(sem_t *sem);
簡單使用
#import "pthread.h"
#import <sys/semaphore.h>
static sem_t *semaphore;
void *runForSemaphoreDemo1(void * arg) {
// 等待信號(hào)
sem_wait(semaphore);
printf("Running - %s.\n", arg);
return 0;
}
void *runForSemaphoreDemo2(void * arg) {
printf("Running - %s.\n", arg);
sleep(1);
// 發(fā)送信號(hào)
sem_post(semaphore);
return 0;
}
- (void)semaphoreDemo {
// sem_init 初始化匿名信號(hào)量在 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用來等待一個(gè)線程的結(jié)束
pthread_join(thread_1, &result);
pthread_join(thread_2, &result);
}
運(yùn)行輸出
Running - Thread 2.
Running - Thread 1.