關(guān)于多線程的介紹、多線程的創(chuàng)建、使用場景和Runloop可以參考《iOS多線程編程指南》。已上傳到GitHub倉庫。
這里主要說明線程同步技術(shù)的鎖,尤其是POSIX互斥鎖。已經(jīng)寫成了Demo,可以對照著看。GitHub地址:https://github.com/xiaoL0204/PthreadsDemo
將來也會從項目中提取更多Demo出來,對應(yīng)不同的多線程知識。
這個Demo適用如下場景:同時向服務(wù)器取不同的數(shù)據(jù),每次回調(diào)以后在子線程中處理數(shù)據(jù)(要共享數(shù)據(jù)),在主線程中顯示數(shù)據(jù)。
因為數(shù)據(jù)的處理是在子線程中,在主線程中UITableView reloadData顯示數(shù)據(jù);必須等到tableView刷新完成以后才能處理下一次數(shù)據(jù)回調(diào),否則在reload data時數(shù)據(jù)源修改了會引起崩潰。
對于這樣的場景,這里考慮使用條件變量進行線程間同步。
原理如下:在數(shù)據(jù)處理子線程中等待條件成立,若不成立則會一直等待,直到主線程刷新完成并發(fā)出激活信號后重新激活數(shù)據(jù)處理子線程;若成立則不會等待,直接進入主線程刷新UI。
Demo 使用了如下變量和函數(shù):
1、pthread_mutex_t
2、pthread_cond_t
3、pthread_cond_wait()
4、pthread_cond_signal()
5、pthread_join()
它們的含義和使用方法如下:
1、pthread_mutex_t 互斥鎖
有兩種方法創(chuàng)建互斥鎖,靜態(tài)方式和動態(tài)方式。
靜態(tài)方式:
使用宏PTHREAD_MUTEX_INITIALIZER來初始化互斥鎖,屬性參數(shù)默認:
pthread_mutex_t mutex_t = PTHREAD_MUTEX_INITIALIZER;
動態(tài)方式:
可以指定互斥鎖屬性:
int pthread_mutex_init(pthread_mutex_t * __restrict,
const pthread_mutexattr_t * _Nullable __restrict);
2、pthread_cond_t 條件變量
運用于線程間同步。一般和pthread_mutex_t一起使用。
可以使用靜態(tài)方式和動態(tài)方式初始化條件變量:
靜態(tài)方式:
用宏PTHREAD_COND_INITIALIZER來初始化靜態(tài)定義的條件變量,使其具有缺省屬性。
動態(tài)方式:
指定條件變量的屬性:
int pthread_cond_init(
pthread_cond_t * __restrict,
const pthread_condattr_t * _Nullable __restrict)
__DARWIN_ALIAS(pthread_cond_init);
3、pthread_cond_wait()函數(shù)
int pthread_cond_wait(pthread_cond_t * __restrict,
pthread_mutex_t * __restrict) __DARWIN_ALIAS_C(pthread_cond_wait);
pthread_cond_wait() 必須與pthread_mutex_t 配套使用。
用于阻塞當(dāng)前線程,等待別的線程使用 pthread_cond_signal() 或pthread_cond_broadcast()來喚醒它。
具體來說,就是函數(shù)將解鎖pthread_mutex_t指向的互斥鎖,并使當(dāng)前線程阻塞在pthread_mutex_t指向的條件變量上。
因此,在使用時,最好的方法是循環(huán)調(diào)用pthread_cond_wait函數(shù),循環(huán)的終止條件為額外定義的變量。如下面核心代碼中的while循環(huán)。
4、pthread_cond_signal()函數(shù)
int pthread_cond_signal(pthread_cond_t *);
作用:激活一個處于阻塞等待狀態(tài)的線程,存在多個阻塞線程時按規(guī)則激活其中第一個。
pthread_cond_signal 函數(shù)會發(fā)送信號給其它阻塞在pthread_cond_t指向的條件變量的線程,阻塞在該條件變量上的線程接收信號后,脫離阻塞狀態(tài),繼續(xù)執(zhí)行后續(xù)代碼。
使用pthread_cond_signal一般不會有“驚群現(xiàn)象”產(chǎn)生,他最多只給一個線程發(fā)信號。假如有多個線程阻塞在這個條件變量的話,會根據(jù)各阻塞線程優(yōu)先級的高低確定哪個接收到信號的線程接繼續(xù)執(zhí)行后續(xù)代碼。如果各線程優(yōu)先級相同,則按入隊順序激活其中第一個。pthread_cond_signal()只會激活最多一個等待該條件的線程。
pthread_cond_signal 在多處理器上可能同時喚醒多個線程,當(dāng)你只能讓一個線程處理某個任務(wù)時,其它被喚醒的線程就需要繼續(xù) wait。所以pthread_cond_wait()需要使用while作為外部判斷。
5、pthread_join()函數(shù)
int pthread_join(pthread_t , void * _Nullable * _Nullable)
__DARWIN_ALIAS_C(pthread_join);
使一個線程等待另一個線程結(jié)束。
其它函數(shù):
6、pthread_cond_timedwait()函數(shù)
int pthread_cond_timedwait(
pthread_cond_t * __restrict, pthread_mutex_t * __restrict,
const struct timespec * _Nullable __restrict)
__DARWIN_ALIAS_C(pthread_cond_timedwait);
函數(shù)到了一定的時間,即使條件未發(fā)生也會解除阻塞。這個時間由參數(shù)abstime指定。
7、pthread_cond_broadcast()函數(shù)
int pthread_cond_broadcast(pthread_cond_t *);
喚醒所有被pthread_cond_wait()函數(shù)阻塞在某個條件變量上的線程,pthread_cond_t指針指向這個條件變量。
主要的方法實現(xiàn):
- (void)fetchHomeData{
self.themeListArr = [NSMutableArray array];
__weak __block typeof(self) weakSelf = self;
NSMutableArray *serverThemeArr = [NSMutableArray array];
dispatch_queue_t queue_t = dispatch_queue_create("com.dispatch.themeserial", DISPATCH_QUEUE_SERIAL);
__block BOOL oneJobdone = YES;
__block pthread_cond_t cond_t = PTHREAD_COND_INITIALIZER;
//為了防止競爭,條件變量的使用總是和一個互斥鎖結(jié)合在一起。
__block pthread_mutex_t mutex_t = PTHREAD_MUTEX_INITIALIZER;
[[XLDataFetchHandler sharedInstance] requestAllHomeThemeListWithCompletion:^(NSString *themeIds,NSArray *themeList, BOOL httpDone) {
dispatch_async(queue_t, ^{
pthread_t threadId = pthread_self();
NSLog(@"requestAllHomeThemeListWithCompletion fetch data! themeIds:%@",themeIds);
while (!oneJobdone) { //為何使用while判斷:防止可能存在的“驚群效應(yīng)”。pthread_cond_wait里的線程可能會被意外喚醒,如果這個時候oneJobdone為NO,則說明UI沒有刷新完成。這個時候,應(yīng)該讓線程繼續(xù)進入pthread_cond_wait
pthread_cond_wait(&cond_t, &mutex_t); // pthread_cond_wait用于阻塞當(dāng)前線程,等待別的線程使用 pthread_cond_signal() 或pthread_cond_broadcast來喚醒它
}
oneJobdone = NO;
[serverThemeArr addObjectsFromArray:themeList];
if (httpDone) {
self.themeListArr = [NSMutableArray arrayWithArray:serverThemeArr];
// [self sortThemeListArray]; //請求結(jié)束,排序
}else{
// [weakSelf filterOriginThemeListWithPartList:themeList]; //一次請求結(jié)束,過濾
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"requestAllHomeThemeListWithCompletion reloadData before");
//table view reload,不知道什么時候結(jié)束。所以要在reload data完成后發(fā)信號
[weakSelf.tableView reloadData];
NSLog(@"requestAllHomeThemeListWithCompletion reloadData after");
oneJobdone = YES;
//對條件變量cond_t發(fā)信號,激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個
pthread_cond_signal(&cond_t);
NSLog(@"requestAllHomeThemeListWithCompletion signal");
});
pthread_join(threadId, NULL);
});
}];
}
運行效果圖:

