關於GCD同步組實現多個異步線程的同步執行中的注意點


在App開發中經常會遇到多個線程同時向服務器取數據, 如果每個線程取得數據后都去刷新UI會造成界面的閃爍

也有可能出現部分數據還沒有獲取完畢造成程序crash

 

之前在網上看到很多是利用dispatch_group_asyncdispatch_group_tdispatch_group_notify 組合來實現的

比如這樣:

將幾個線程加入到group中, 然后利用group_notify來執行最后要做的動作

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //創建一個group
    dispatch_group_t group = dispatch_group_create();
    
    //創建一個隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //創建一個GCD線程1
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"線程1");
    });
    
    //創建一個GCD線程2
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"線程2");
    });
    
    //創建一個GCD線程3
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"線程3");
    });
    
    //創建一個group通知
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"結束");
    });
}

運行結果:

2017-01-18 11:49:22.454 GCDDemo[1375:107838] 線程2
2017-01-18 11:49:22.454 GCDDemo[1375:107837] 線程3
2017-01-18 11:49:22.454 GCDDemo[1375:107840] 線程1
2017-01-18 11:49:22.454 GCDDemo[1375:107840] 結束

看起來是3個線程無序運行, 最后等全部線程結束后才執行group結束動作. 看樣子都很正常

 

但如果3個線程為異步操作呢, 比如網絡請求

我們用異步計數試試看

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //創建一個group
    dispatch_group_t group = dispatch_group_create();
    
    //創建一個隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //創建一個GCD線程1
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程1");
        });
    });
    
    //創建一個GCD線程2
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程2");
        });
    });
    
    //創建一個GCD線程3
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程3");
        });
    });
    
    //創建一個group通知
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"結束");
    });
}

運行結果為:

2017-01-18 13:31:57.610 GCDDemo[1528:138162] 結束
2017-01-18 13:31:57.621 GCDDemo[1528:138079] 線程1
2017-01-18 13:31:57.621 GCDDemo[1528:138079] 線程2
2017-01-18 13:31:57.622 GCDDemo[1528:138079] 線程3

看, 這樣就出問題了 先運行了我們原本要等線程都完成后才執行的動作

 

那要如何解決這個問題呢?

 

正確的方法應該是以上三個函數再配合

dispatch_group_enter(group)dispatch_group_leave(group)兩個函數一起來使用,這樣才能實現我們想要的最終效果。

 

dispatch_group_enter(dispatch_group_t group)
參數group不能為空,在異步任務開始前調用。

它明確的表明了一個 block 被加入到了隊列組group中,此時group中的任務的引用計數會加1(類似於OC的內存管理)

dispatch_group_enter(group)必須與dispatch_group_leave(group)配對使用,

它們可以在使用dispatch_group_async時幫助你合理的管理隊列組中任務的引用計數的增加與減少。

 

dispatch_group_leave(dispatch_group_t group)
參數group不能為空,在異步任務成功返回后調用。

它明確的表明了隊列組里的一個 block 已經執行完成,隊列組中的任務的引用計數會減1,

它必須與dispatch_group_enter(group)配對使用,dispatch_group_leave(group)的調用次數不能多於dispatch_group_enter(group)的調用次數。

當隊列組里的任務的引用計數等於0時,會調用dispatch_group_notify函數。

 

我們試試看, 注意紅色字體代碼

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //創建一個group
    __block dispatch_group_t group = dispatch_group_create();
    
    //創建一個隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //group任務計數加3
    dispatch_group_enter(group);
    dispatch_group_enter(group);
    dispatch_group_enter(group);
    
    //創建一個GCD線程1
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程1");
            //group任務計數減1
 dispatch_group_leave(group);
        });
    });
    
    //創建一個GCD線程2
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程2");
            //group任務計數減1
 dispatch_group_leave(group);
        });
    });
    
    //創建一個GCD線程3
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程3");
            //group任務計數減1
 dispatch_group_leave(group);
        });
    });
    
    //創建一個group通知, 任務計數為0時自動調用
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"結束");
    });
}

運行結果:

2017-01-18 13:46:59.988 GCDDemo[1564:144979] 線程1
2017-01-18 13:46:59.991 GCDDemo[1564:144979] 線程2
2017-01-18 13:46:59.991 GCDDemo[1564:144979] 線程3
2017-01-18 13:46:59.993 GCDDemo[1564:145035] 結束

這樣就符合我們的預期了

 

還沒結束, 不  上面的方法是可以正確的實現多線程同步了, 現在我們再看下另外一種解決辦法

利用GCD信號量dispatch_semaphore_t來實現,

我們先看下什么是信號量

首先了解下信號量的幾個方法
1.dispatch_semaphore_create(long value);
   創建信號量,傳入的value值要大於等於0,返回一個信號量
 2.dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
   如果信號量的value值大於0,則會往下執行並將value的值減1,否則,阻礙當前線程並等待timeout后再往下執行。如果等待的期間desema的值被dispatch_semaphore_signal函數加1了,且該函數所處線程獲得了信號量,那么就繼續向下執行並將信號量減1。如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程會自動往下執行。
 3.dispatch_semaphore_signal(dispatch_semaphore_t dsema);
   返回值為long類型,當返回值為0時表示當前並沒有線程等待其處理的信號量,其處理的信號量的值加1即可。當返回值不為0時,表示其當前有(一個或多個)線程等待其處理的信號量,並且該函數喚醒了一個等待的線程(當線程有優先級時,喚醒優先級最高的線程;否則隨機喚醒)。

實現過程:

  • 創建一個任務組dispatch_group
 dispatch_group_t group = dispatch_group_create();
  • 將每個請求包裝成一個任務異步提交到任務組里,每個任務在一開始創建一個信號量,value值為0,任務最后在網絡請求完成前進行信號量的等待,如果網絡請求完成,則調用 'dispatch_semaphore_signal(semaphore);'對信號值加1,則線程不再進行信號量的等待,繼續往下執行。
  • 當所有請求都完成時,會在dispatch_group_notify里的回調進行相應的處理。

我們上代碼看看:

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //創建一個group
    __block dispatch_group_t group = dispatch_group_create();
    
    //創建一個隊列
    __block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //創建一個信號量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    //創建一個GCD線程1
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程1");
            //完成迭代后, 增加信號量
 dispatch_semaphore_signal(semaphore);
        });
        
        //在迭代完成之前, 信號量等待
 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    //創建一個GCD線程2
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程2");
            //完成迭代后, 增加信號量
 dispatch_semaphore_signal(semaphore);
        });
        
        //在迭代完成之前, 信號量等待
 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    //創建一個GCD線程3
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程3");
            //完成迭代后, 增加信號量
 dispatch_semaphore_signal(semaphore);
        });
        
        //在迭代完成之前, 信號量等待
 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    //創建一個group通知, 任務計數為0時自動調用
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"結束");
    });
}

這樣也實現了同步實現異步線程, 可能大家會有一個疑問, 不同線程之前的信號量是否會相互干擾呢,

或者說如果其中一個線程要耗費相當大的時間, 其他線程是否也會被阻塞呢, 

我們來試驗下, 給線程3多增加幾個迭代, 然后在wait前后加上一下打印

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //創建一個group
    __block dispatch_group_t group = dispatch_group_create();
    
    //創建一個隊列
    __block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //創建一個信號量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    //創建一個GCD線程1
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程1");
            //完成迭代后, 增加信號量
            dispatch_semaphore_signal(semaphore);
        });
        
        //在迭代完成之前, 信號量等待
        NSLog(@"線程1等待");
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"線程1完成");
    });
    
    //創建一個GCD線程2
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程2");
            //完成迭代后, 增加信號量
            dispatch_semaphore_signal(semaphore);
        });
        
        //在迭代完成之前, 信號量等待
        NSLog(@"線程2等待");
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"線程2完成");
        
    });
    
    //創建一個GCD線程3
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 100000; i ++) {
                for (int i = 0; i < 100000; i ++) {
                    
                }
            }
            NSLog(@"線程3");
            //完成迭代后, 增加信號量
            dispatch_semaphore_signal(semaphore);
        });
        
        //在迭代完成之前, 信號量等待
        NSLog(@"線程3等待");
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"線程3完成");
    });
    
    //創建一個group通知, 任務計數為0時自動調用
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"結束");
    });
}

 

運行結果為:

2017-01-18 17:14:58.814 GCDDemo[1136:77412] 線程2等待
2017-01-18 17:14:58.814 GCDDemo[1136:77410] 線程1等待
2017-01-18 17:14:58.814 GCDDemo[1136:77409] 線程3等待
2017-01-18 17:14:58.823 GCDDemo[1136:77339] 線程1
2017-01-18 17:14:58.823 GCDDemo[1136:77339] 線程2
2017-01-18 17:14:58.823 GCDDemo[1136:77412] 線程2完成
2017-01-18 17:14:58.823 GCDDemo[1136:77410] 線程1完成
2017-01-18 17:15:17.793 GCDDemo[1136:77339] 線程3
2017-01-18 17:15:17.793 GCDDemo[1136:77409] 線程3完成
2017-01-18 17:15:17.794 GCDDemo[1136:77409] 結束

 

好像看起來線程1, 2沒有受到線程3的影響

我們再增加線程3的耗時看看, 

 

//創建一個GCD線程3
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 1000000; i ++) {
                for (int i = 0; i < 1000000; i ++) {
                    
                }
            }
            NSLog(@"線程3");
            //完成迭代后, 增加信號量
            dispatch_semaphore_signal(semaphore);
        });
        
        //在迭代完成之前, 信號量等待
        NSLog(@"線程3等待");
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"線程3完成");
    });

 

我們多運行幾次, 發現會出現這樣的結果

2017-01-18 17:24:37.975 GCDDemo[1207:83821] 線程3等待
2017-01-18 17:24:37.975 GCDDemo[1207:83829] 線程2等待
2017-01-18 17:24:37.975 GCDDemo[1207:83818] 線程1等待
2017-01-18 17:24:37.984 GCDDemo[1207:83786] 線程1
2017-01-18 17:24:37.984 GCDDemo[1207:83786] 線程2
2017-01-18 17:24:37.984 GCDDemo[1207:83821] 線程3完成
2017-01-18 17:24:37.985 GCDDemo[1207:83829] 線程2完成

線程3先打印了執行完, 所以看不同線程去偵測同一個信號量的時候是會有干擾的, 但是還是會等全部線程執行結束后才會去執行notify動作

 

那給每一個線程分別創建一個信號量呢?

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //創建一個group
    __block dispatch_group_t group = dispatch_group_create();
    
    //創建一個隊列
    __block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //創建一個信號量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
    dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
    
    //創建一個GCD線程1
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程1");
            //完成迭代后, 增加信號量
            dispatch_semaphore_signal(semaphore);
        });
        
        //在迭代完成之前, 信號量等待
        NSLog(@"線程1等待");
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"線程1完成");
    });
    
    //創建一個GCD線程2
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 10000; i ++) {
                
            }
            NSLog(@"線程2");
            //完成迭代后, 增加信號量
            dispatch_semaphore_signal(semaphore2);
        });
        
        //在迭代完成之前, 信號量等待
        NSLog(@"線程2等待");
        dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
        NSLog(@"線程2完成");
        
    });
    
    //創建一個GCD線程3
    dispatch_group_async(group, queue, ^{
        
        //模擬異步耗時操作
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 1000000; i ++) {
                for (int i = 0; i < 1000000; i ++) {
                    
                }
            }
            NSLog(@"線程3");
            //完成迭代后, 增加信號量
            dispatch_semaphore_signal(semaphore3);
        });
        
        //在迭代完成之前, 信號量等待
        NSLog(@"線程3等待");
        dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
        NSLog(@"線程3完成");
    });
    
    //創建一個group通知, 任務計數為0時自動調用
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"結束");
    });
}

多運行幾次, 看起來每次都是只有線程3等待, 1, 2線程會自己正常完成

這樣就OK了,  所以盡量每一個線程創建一個信號量, 避免相互干擾

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM