我們看圖片只是樂呵一下,程序猿思考問題差不多就是這個樣子,
今天同事在線程通信這一塊有點疑問,我們下面來分析一下,系統都提供給我們那些,其實我們都知道,但是很少去關注這些API,也正是這些API,來回在APP中去執行各種不同的線程和隊列
一、常見的線程間通信 GCD
我們先來看一個系統的例子:
//開啟一個全局隊列的子線程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//1. 開始請求數據
//...
// 2. 數據請求完畢
//我們知道UI的更新必須在主線程操作,所以我們要從子線程回調到主線程
dispatch_async(dispatch_get_main_queue(), ^{
//我已經回到主線程更新
});
});
如上所述,我們下面進行一個測試:
//自定義隊列,開啟新的子線程
dispatch_queue_t custom_queue = dispatch_queue_create("concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
for (int i=0; i<10; i++) {
dispatch_async(custom_queue, ^{
NSLog(@"## 并行隊列 %d ##",i);
//數據更新完畢回調主線程 線程之間的通信
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"### 我在主線程 通信 ##");
});
});
}
- 線程中延遲調用某個方法
//線程延遲調用 通信
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"## 在主線程延遲5秒調用 ##");
});
線程休眠幾秒的方法
sleep(6);
: 這里是休眠6秒-
常用的線程通信方法有以下幾種:(GCD)
- 需要更新UI操作的時候使用下面這個GCD的block方法
//回到主線程更新UI操作 dispatch_async(dispatch_get_main_queue(), ^{ //數據執行完畢回調到主線程操作UI更新數據 });
- 有時候省去麻煩,我們使用系統的全局隊列:一般用這個處理遍歷大數據查詢操作
DISPATCH_QUEUE_PRIORITY_HIGH 全局隊列高優先級 DISPATCH_QUEUE_PRIORITY_LOW 全局隊列低優先級 DISPATCH_QUEUE_PRIORITY_BACKGROUND 全局隊里后臺執行隊列 // 全局并發隊列執行處理大量邏輯時使用 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ });
- 當在開發中遇到一些數據需要單線程訪問的時候,我們可以采取同步線程的做法,來保證數據的正常執行
//當我們需要執行一些數據安全操作寫入的時候,需要同步操作,后面所有的任務要等待當前線程的執行 dispatch_sync(dispatch_get_global_queue(0, 0), ^{ //同步線程操作可以保證數據的安全完整性 });
二、了解一下NSObject中的對象線程訪問模式
-
我們介紹簡單的perfermselecter選擇器實現線程通信
//數據請求完畢回調到主線程,更新UI資源信息 waitUntilDone 設置YES ,代表等待當前線程執行完畢 [self performSelectorOnMainThread:@selector(dothing:) withObject:@[@"1"] waitUntilDone:YES];
-
如果需要執行到后臺線程,則直接前往后臺線程執行即可
//將當前的邏輯轉到后臺線程去執行 [self performSelectorInBackground:@selector(dothing:) withObject:@[@"2"]];
自己定義線程,將當前數據轉移到指定的線程內去通信操作
//支持自定義線程通信執行相應的操作
NSThread * thread = [[NSThread alloc]init];
[thread start];
//當我們需要在特定的線程內去執行某一些數據的時候,我們需要指定某一個線程操作
[self performSelector:@selector(dothing:) onThread:thread withObject:nil waitUntilDone:YES];
//支持自定義線程通信執行相應的操作
NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(testThread) object:nil];
[thread start];
//當我們需要在特定的線程內去執行某一些數據的時候,我們需要指定某一個線程操作
[self performSelector:@selector(dothing:) onThread:thread withObject:nil waitUntilDone:YES];
- 上面幾種方法就是我們常用的對象調用常用的線程間通信,我們可以根據不同的情況,采取不同的線程執行狀態.
增加一個特殊的線程常駐RunLoop 的做法
- 需求: 我們經常要用到常駐線程來請求數據,但是請求有時候在線程會退出,這個時候我們需要用一下方法:
//有時候需要線程單獨跑一個RunLoop 來保證我們的請求對象存在,不至于會被系統釋放掉
NSThread * runLoopThread = [[NSThread alloc]initWithTarget:self selector:@selector(entryThreadPoint) object:nil];
[runLoopThread start];
[self performSelector:@selector(handleMutiData) onThread:runLoopThread withObject:nil waitUntilDone:YES];
//給線程增加一個run loop 來保持對象的引用
- (void)entryThreadPoint{
@autoreleasepool {
[NSThread currentThread].name = @"自定義線程名字";
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
}
}
- (void)handleMutiData{
NSLog(@"## 我是跑在runloop的線程 ##");
}
最后測試截圖如下,看下我們的線程是不是已經加入runloop 了
補充1:
- 有個地方需要補充以下: 請先看完下面的之后,再回頭看這個地方。
需求: 如何動態的調整隊列的優先級,執行層級,我們這里多加一個函數
// 設置目標隊列 queue 既是target , object 是我們將要指定的dispatch 對象, 第一個參數要用自定義的隊列,不能使用系統的全局隊列,系統的隊列層級我們無法修改
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
這個就是我們指定一個隊列執行在目標隊列上,和目標隊列一個層級
我們看下代碼如何調用:
dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
dispatch_set_target_queue(queue1, queue2);
dispatch_async(queue2, ^{
NSLog(@"## 我是queue2 ----0###");
});
dispatch_async(queue1, ^{
NSLog(@"## 我是queue1 ------1##");
});
dispatch_async(queue2, ^{
NSLog(@"## 我是queue2 ----2 ##");
});
dispatch_async(queue1, ^{
NSLog(@"## 我是queue1 ------3 ##");
});
dispatch_async(queue1, ^{
NSLog(@"## 我是queue1 ------4 ##");
});
dispatch_async(queue2, ^{
NSLog(@"## 我是queue2 ---5 ##");
});
- 我們可以先猜想一下輸出結果,會按照我們想要的結果顯示么
答案:
2016-06-01 11:53:36.333 RunTimeModify[41214:6432344] ## 我是queue2 ----0###
2016-06-01 11:53:36.339 RunTimeModify[41214:6432344] ## 我是queue1 ------1##
2016-06-01 11:53:36.341 RunTimeModify[41214:6432344] ## 我是queue1 ------3 ##
2016-06-01 11:53:36.343 RunTimeModify[41214:6432344] ## 我是queue1 ------4 ##
2016-06-01 11:53:36.344 RunTimeModify[41214:6432344] ## 我是queue2 ----2 ##
2016-06-01 11:53:36.344 RunTimeModify[41214:6432344] ## 我是queue2 ---5 ##
相信大家發現問題了。
我們調整一下Target 的順序:
dispatch_set_target_queue(queue2, queue1);
我們再來看一下輸出:
2016-06-01 11:57:30.483 RunTimeModify[41297:6435226] ## 我是queue2 ----0###
2016-06-01 11:57:30.486 RunTimeModify[41297:6435226] ## 我是queue2 ----2 ##
2016-06-01 11:57:30.486 RunTimeModify[41297:6435226] ## 我是queue2 ---5 ##
2016-06-01 11:57:30.487 RunTimeModify[41297:6435226] ## 我是queue1 ------1##
2016-06-01 11:57:30.487 RunTimeModify[41297:6435226] ## 我是queue1 ------3 ##
2016-06-01 11:57:30.487 RunTimeModify[41297:6435226] ## 我是queue1 ------4 ##
以上只是簡單的使用,我們不做什么的測試,了解就行:
以上這個地方,大家還會有個小疑問:連接在這里,stack 上找的How does dispatch_set_target_queue work?
大家看到這個或許就明白怎么工作的了
補充2:
謝謝 @激動的馬 問題 ,還要增加一種通信方法
NSOperationQueue 系統自帶的更新UI的方法 ,這種我不經常用,推薦GCD
//注意點 : 設計到UI的更新在主隊列
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"### 我是在主隊列執行的block ####");
}];
//這個只是用作判斷當前線程是否是主線程
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"## 我是主線程 ##");
}else{
NSLog(@"## 我是子線程 ###");
}
補充三、
- GCD中阻塞線程之用法
//主要涉及到下面這個API,該API是保證自己的block會等待當前隊列之前的block全部執行完畢,在執行自己的block,最后在執行后面的任務
void
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
我們用代碼簡單的看一下調用順序:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 全局隊列異步執行 第1個任務 ##");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 全局隊列異步執行 第2個任務 ##");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 全局隊列異步執行 第4個任務 ##");
});
dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 異步阻塞當前線程,會等待當前線程執行完畢 ###");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"### 我在全局隊列線程內執行 第3個任務###");
});
* 我們看下控制臺輸出:
2016-06-01 14:04:13.973 RunTimeModify[42377:6489044] ## 全局隊列異步執行 第1個任務 ##
2016-06-01 14:04:13.973 RunTimeModify[42377:6489051] ## 全局隊列異步執行 第2個任務 ##
2016-06-01 14:04:13.973 RunTimeModify[42377:6489063] ## 全局隊列異步執行 第4個任務 ##
2016-06-01 14:04:13.973 RunTimeModify[42377:6489064] ## 異步阻塞當前線程,會等待當前線程執行 完畢 ###
2016-06-01 14:04:13.973 RunTimeModify[42377:6489065] ### 我在全局隊列線程內執行 第3個任務###
大概可以看一下阻塞這個API是怎么用的,可以在項目總很好的處理我們的問題
- 我們在繼續看下面的循環執行的block任務:
for (int i=0; i<5; i++) {
dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 我是阻塞線程執行順序 %d ###",i);
});
}
for (int i=0; i<5; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"### 我是全部異步隊列任務 : %d ###",i);
});
}
我們看下控制臺打印輸出的日志系統:
2016-06-01 14:07:42.534 RunTimeModify[42437:6491161] ## 我是阻塞線程執行順序 1 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491166] ## 我是阻塞線程執行順序 0 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491168] ## 我是阻塞線程執行順序 2 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491179] ## 我是阻塞線程執行順序 3 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491180] ## 我是阻塞線程執行順序 4 ###
2016-06-01 14:07:42.535 RunTimeModify[42437:6491181] ### 我是全部異步隊列任務 : 0 ###
2016-06-01 14:07:42.535 RunTimeModify[42437:6491161] ### 我是全部異步隊列任務 : 1 ###
2016-06-01 14:07:42.536 RunTimeModify[42437:6491166] ### 我是全部異步隊列任務 : 2 ###
2016-06-01 14:07:42.536 RunTimeModify[42437:6491182] ### 我是全部異步隊列任務 : 3 ###
2016-06-01 14:07:42.537 RunTimeModify[42437:6491168] ### 我是全部異步隊列任務 : 4 ###
雖然都是異步任務,但是執行的很有規則。我們可以去嘗試測試一下反過來的結果
- 可以測試下面這個執行順序:
//測試一下,是不是阻塞API之前的任務必須要執行完畢,看下有什么問題
for (int i=0; i<5; i++) {
dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 我是阻塞線程執行順序 %d ###",i);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"### 我是全部異步隊列任務 : %d ###",i);
});
}
- 經常遇到的問題:
- 網絡請求到數據,但是UI沒有及時更新。
- 在子線程的block內執行更新UI操作,控制臺一般都會有錯誤線程輸出的
- 如果只是邏輯的處理,無關UI,處理數據量較大,可以選擇子線程處理。
本文暫時先介紹到這里,如果還想了解用信號量控制線程可以參考另外一篇博客:關于dispatch 下一遍補充了源的問題
iOS 關于dispatch_semaphore_t 和 dispatch_group_t 的簡單實用,用于多網絡異步回調通知