Qinz
關于多線程的概念網上很多資料可供學習,下面我們主要講解幾種工作中會經常遇到的多線程問題及解決思路。
一、一句話簡單理解相關概念
進程:手機中的一個APP就是一個進程。
線程:一個進程可以開啟多個線程,默認開啟主線程。
隊列: 隊形結構。分串行和并發,按照 FIFO(先進先調度)執行任務。
同步: 按順序執行,不開啟新線程。
異步: 不按順序執行,可以開啟新線程。
串行: 在線程中依次執行。
并發: 多條線程同時開工。
-
GCD應用
1. 經典現象之死鎖
- (void)test{
NSLog(@"-----任務1----");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"-----任務2----");
});
NSLog(@"-----任務3----");
}
分析:控制臺打印"-----任務1----"后崩潰。因為默認就一個主線程和一個主隊列,任務2要等待test執行完才執行,而test的執行又依賴于任務2,所以出現相互等待,造成崩潰。
2. 經典場景之賣票
- (void)viewDidLoad {
[super viewDidLoad];
// 第一個線程賣票
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self saleTickets];
});
// 第一個線程賣票
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self saleTickets];
});
}
// 售票
- (void)saleTickets {
while (self.tickets > 0) {
//模擬延遲
[NSThread sleepForTimeInterval:1];
//判斷是否還有余票
if (self.tickets > 0) {
//如果有票,賣一張,提示用戶
self.tickets--;
NSLog(@"剩余票數 %zd %@",self.tickets,[NSThread currentThread]);
}else{
NSLog(@"沒有票,來晚了%@",[NSThread currentThread]);
break;
}
}
}
- 2.1 控制臺部分輸出,可以看到出現了資源搶奪
剩余票數 10 <NSThread: 0x600000970700>{number = 4, name = (null)}
剩余票數 11 <NSThread: 0x600000978140>{number = 3, name = (null)}
剩余票數 9 <NSThread: 0x600000970700>{number = 4, name = (null)}
剩余票數 9 <NSThread: 0x600000978140>{number = 3, name = (null)}
- 2.2 用同步串行解決如下,當然這里也可以使用 @synchronized加鎖進行實現,蘋果并不推薦使用加鎖方式。
while (self.tickets > 0) {
//同步串行
dispatch_sync(dispatch_queue_create("Qinz", DISPATCH_QUEUE_SERIAL), ^{
//模擬延遲
[NSThread sleepForTimeInterval:1];
//判斷是否還有余票
if (self.tickets > 0) {
//如果有票,賣一張,提示用戶
self.tickets--;
NSLog(@"剩余票數 %zd %@",self.tickets,[NSThread currentThread]);
}else{
NSLog(@"沒有票,來晚了%@",[NSThread currentThread]);
}
});
}
3. 使用柵欄函數解決常見多任務依賴問題
-(void)barrierDemo{
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.Qinz.cn", DISPATCH_QUEUE_CONCURRENT);
/** 異步函數,模擬多任務網絡請求 */
dispatch_async(concurrentQueue, ^{
for (int i = 0; i<10; i++) {
NSLog(@"我在下載圖片%d---當前線程%@",i,[NSThread currentThread]);
}
});
/** 異步函數,模擬多任務網絡請求 */
dispatch_async(concurrentQueue, ^{
for (int i = 0; i<8; i++) {
NSLog(@"我在請求商品詳情數據%d--當前線程%@",i,[NSThread currentThread]);
}
});
/** 柵欄函數:相當于堵塞前面的操作 */
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"前面的異步任務執行完畢-----------%@",[NSThread currentThread]);
});
/** 處理日常任務 */
dispatch_async(concurrentQueue, ^{
for (int i = 0; i<8; i++) {
NSLog(@"日常任務處理%d--當前線程%@",i,[NSThread currentThread]);
}
});
}
柵欄函數使用注意:
- 一定要是自定義的并發隊列,使用系統提供的全局并發隊列會達不到想要的效果。
- 必須要求任務都在同一個隊列中執行。
4. 使用調度組解決多任務依賴問題
-(void)gcdGroup{
//創建調度組
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
for (int i = 0; i<500; i++) {
NSLog(@"我在下載圖片%d---當前線程%@",i,[NSThread currentThread]);
}
});
//允許等待的任務執行的時間
long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW,3 * NSEC_PER_SEC));
//如果dispatch_group_wait函數返回值為0,就意味著Dispatch Group中的操作全部執行完畢
if (timeout == 0) {
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"---刷新UI----");
});
}else{
NSLog(@"---你超時了----");
}
}
5. 使用GCD的_enter和_leave解決多任務依賴問題
-(void)gcdEnter{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
//底層有一個signal,進組就會+1.判斷signal是否為0,出組signal=0
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (int i = 0; i<10; i++) {
NSLog(@"我在下載圖片%d---當前線程%@",i,[NSThread currentThread]);
}
NSLog(@"??---第一個任務執行完畢 ----??");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (int i = 0; i<8; i++) {
NSLog(@"我在請求商品詳情數據%d--當前線程%@",i,[NSThread currentThread]);
}
NSLog(@"??---第二個任務執行完畢 ----??");
dispatch_group_leave(group);
});
//信號為0,才進入通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任務完成,可以更新UI了");
});
}
注意:_enter和_leave要成對出現,否則會造成最終不走通知回調或程序崩潰。
6. 當我們要控制并發數時,使用信號量來進行實現
#pragma mark - 信號量控制并發數
-(void)gcdSemaphore{
/**
通過信號量來控制并發數,當信號量為2時,最大允許2兩條線程同時執行;當信號量為1時,可以達到加鎖的目的,實現同步效果。
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//任務1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"---- 任務1開始執行 ----");
sleep(1);
NSLog(@"??---- 任務1執行完畢 ----");
dispatch_semaphore_signal(semaphore);
});
//任務2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"---- 任務2開始執行 ----");
sleep(1);
NSLog(@"??---- 任務2執行完畢 ----");
dispatch_semaphore_signal(semaphore);
});
//任務3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"---- 任務3開始執行 ----");
sleep(1);
NSLog(@"??---- 任務3執行完畢 ----");
dispatch_semaphore_signal(semaphore);
});
}
-
NSOperation應用
1. 線程之間的通訊
#pragma mark - 線程通訊
-(void)communication{
NSOperationQueue*queue = [[NSOperationQueue alloc]init];
queue.name = @"Qinz";
[queue addOperationWithBlock:^{
NSLog(@"??當前隊列---%@-當前線程---%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
//模擬網路請求
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"h??當前隊列---%@-當前線程---%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
}];
}];
}
2. 控制并發數
#pragma mark - 控制并發數
-(void)concurrent{
NSOperationQueue*quue = [[NSOperationQueue alloc]init];
//控制并發數
quue.maxConcurrentOperationCount = 3;
for (int i = 0; i < 30; i++) {
[quue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"??--%d--當前線程---%@",i,[NSThread currentThread]);
}];
}
}
3. 解決多任務依賴問題
#pragma mark - 多任務依賴順序執行
-(void)depend{
NSBlockOperation * bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"---- 請求數據,拿到token ------");
}];
NSBlockOperation * bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"----- 拿到token,請求數據1 ------");
}];
NSBlockOperation * bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"------ 拿到1,請求數據2 -----");
}];
//建立依賴
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"--- 所有任務執行完畢 --------");
}
多線程還有很多用法值得我們去探索,以上只是針對實際開發常見問題進行分析和給出解決思路。
我是Qinz,希望我的文章對你有幫助。