[iOS]多線程--GCD

關于GCD網上很多介紹, 大部分人都很熟悉, 這里只是本人學習的一個總結, 不到之處,還請指正.

一. 準備

在這之前, 我們需要明白幾個比較容易混淆的概念:同步, 異步, 并發, 串行;

  • 同步和異步決定了要不要開啟新的線程
    1.同步: 在當前線程中執行任務, 不具備開啟新線程的能力
    2.異步: 在新的線程中執行任務, 具備開啟新線程的能

  • 并發和串行決定了任務的執行方式
    1.并發: 多個任務并發(同時)執行
    2.串行: 一個任務執行完畢后, 再執行下一個任務(順序執行)

明白了這幾個概念, 再去看GCD相關的一些內容就會比較清晰了.

二. CGD中的隊列

1. 串行隊列

GCD中獲取串行隊列有兩種途徑:
第一種是使用dispatch_queue_create函數創建串行隊列:

dispatch_queue_t  dispatch_queue_create(const char *label,  dispatch_queue_attr_t attr);

參數:

  • label: 隊列名稱, 是一個C的字符串, 可自定義
  • attr: 隊列類型, 如果傳NULL, 就是串行隊列; 傳DISPATCH_QUEUE_CONCURRENT,可創建并行隊列

例如:

//創建串行隊列
    dispatch_queue_t  queue= dispatch_queue_create("串行隊列名稱", NULL);
// 非ARC需要釋放手動創建的隊列, 現在一般用不到
 dispatch_release(queue); 

第二種就是直接使用主隊列(跟主線程相關聯的隊列)
主隊列是GCD自帶的一種特殊的串行隊列, 放在主隊列中的任務,都會放到主線程中執行, 使用dispatch_get_main_queue()獲得主隊列;
例如:

// 獲取主隊列(串行隊列)
dispatch_queue_t queue = dispatch_get_main_queue();
2. 并行隊列

并行隊列的獲取也有兩種方式, 第一種上面也有提到, 就是使用 dispatch_queue_create 來創建, 主要第二個參數:

dispatch_queue_t queue = dispatch_queue_create("并發隊列",  DISPATCH_QUEUE_CONCURRENT);

但是, 我們一般不使用這個方式來獲取并行隊列, 而是使用系統預定義的全局并發隊列;
使用dispatch_get_global_queue函數獲得全局的并發隊列:

 dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);;

參數:

  • identifier: 隊列的優先級, 系統預定義了四種, 以供開發者使用
//全局并發隊列的優先級
 #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(中)
 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
  • flags: 這個參數是預留給以后使用的, 暫時用不上, 傳 0 即可;

例如:

 // 獲得默認優先級的全局并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3. 同步, 異步

同步異步的開啟主要是使用下面的函數:

void
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);

同步執行函數, 參數:

  • queue: 在哪個隊列執行
  • block: 執行的任務
void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

異步執行函數, 參數:

  • queue: 在哪個隊列執行
  • block: 執行的任務

三. 同步, 異步, 串行, 并發組合測試

下面就來通過實例看一下, 同步, 異步, 串行, 并發直接的一些聯系和區別;

測試一: 用同步函數往串行隊列中添加任務

不會開啟新的線程:

NSLog(@"用同步函數往串行隊列中添加任務");
    //打印主線程
     NSLog(@"主線程----%@",[NSThread mainThread]);

     //創建串行隊列
    dispatch_queue_t  queue= dispatch_queue_create("LZQueueName", NULL);

     //2.添加任務到隊列中執行
     dispatch_sync(queue, ^{
        NSLog(@"下載圖片1----%@",[NSThread currentThread]);
         sleep(3);
    });
    dispatch_sync(queue, ^{
          NSLog(@"下載圖片2----%@",[NSThread currentThread]);
        sleep(2);
     });
    dispatch_sync(queue, ^{
        NSLog(@"下載圖片3----%@",[NSThread currentThread]);
     });

輸出:

2017-01-06 10:11:32.135 LZGCDTest[2938:57629] 用同步函數往串行隊列中添加任務
2017-01-06 10:11:32.136 LZGCDTest[2938:57629] 主線程----<NSThread: 0x608000066240>{number = 1, name = main}
2017-01-06 10:11:32.136 LZGCDTest[2938:57629] 下載圖片1----<NSThread: 0x608000066240>{number = 1, name = main}
2017-01-06 10:11:35.209 LZGCDTest[2938:57629] 下載圖片2----<NSThread: 0x608000066240>{number = 1, name = main}
2017-01-06 10:11:37.280 LZGCDTest[2938:57629] 下載圖片3----<NSThread: 0x608000066240>{number = 1, name = main}

可以看到, 是按照任務的添加順序執行的, 而且是在當前線程(主線程)中執行的, 沒有開啟新線程(同步函數不具備開啟新線程的能力);

測試二: 用同步函數往并發隊列中添加任務

不會開啟新的線程((同步函數不具備開啟新線程的能力)),并發隊列失去了并發的功能:

//打印主線程
     NSLog(@"主線程----%@",[NSThread mainThread]);

     //獲取并行隊列
     dispatch_queue_t  queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


    // 添加任務到隊列中執行
     dispatch_sync(queue, ^{
         NSLog(@"下載圖片1----%@",[NSThread currentThread]);
         sleep(3);
     });
    dispatch_sync(queue, ^{
         NSLog(@"下載圖片2----%@",[NSThread currentThread]);
        sleep(2);
     });
     dispatch_sync(queue, ^{
        NSLog(@"下載圖片3----%@",[NSThread currentThread]);
     });

輸出:

2017-01-06 10:08:37.874 LZGCDTest[2859:55553] 主線程----<NSThread: 0x60800007adc0>{number = 1, name = main}
2017-01-06 10:08:42.892 LZGCDTest[2859:55553] 下載圖片1----<NSThread: 0x60800007adc0>{number = 1, name = main}
2017-01-06 10:08:45.966 LZGCDTest[2859:55553] 下載圖片2----<NSThread: 0x60800007adc0>{number = 1, name = main}
2017-01-06 10:08:48.035 LZGCDTest[2859:55553] 下載圖片3----<NSThread: 0x60800007adc0>{number = 1, name = main}

可以看出, 雖然使用的是并發隊列, 但是使用的是同步函數, 由于同步函數沒有開啟新線程的能力, 所以并發隊列就失去了并發性, 按照任務的添加順序, 順序執行;

測試三. 用異步函數往串行隊列中添加任務

會開啟線程,但是只開啟一個線程:

 //打印主線程
     NSLog(@"主線程----%@",[NSThread mainThread]);

     //創建串行隊列,iOS4.3之后:第二個參數可寫  DISPATCH_QUEUE_SERIAL,之前只能NULL
     dispatch_queue_t  queue= dispatch_queue_create("LZQueueName", NULL);

     //2.添加任務到隊列中執行
     dispatch_async(queue, ^{
         
        NSLog(@"下載圖片1----%@",[NSThread currentThread]);
         sleep(3);
     });
     dispatch_async(queue, ^{
         
        NSLog(@"下載圖片2----%@",[NSThread currentThread]);
         sleep(2);
     });
    dispatch_async(queue, ^{
         NSLog(@"下載圖片3----%@",[NSThread currentThread]);
     });

輸出:

2017-01-06 10:14:28.944 LZGCDTest[3019:60109] 主線程----<NSThread: 0x608000065440>{number = 1, name = main}
2017-01-06 10:14:33.331 LZGCDTest[3019:60150] 下載圖片1----<NSThread: 0x60000006f2c0>{number = 3, name = (null)}
2017-01-06 10:14:36.404 LZGCDTest[3019:60150] 下載圖片2----<NSThread: 0x60000006f2c0>{number = 3, name = (null)}
2017-01-06 10:14:38.478 LZGCDTest[3019:60150] 下載圖片3----<NSThread: 0x60000006f2c0>{number = 3, name = (null)}

可以看到, 任務沒有在主線程里執行, 開啟了一個新的線程, 雖然是異步, 但是隊列是串行隊列, 所以任務還是按照添加的順序, 順序執行的;

測試四. 用異步函數往并發隊列中添加任務

同時開啟多個子線程執行任務:

//打印主線程
    NSLog(@"主線程----%@",[NSThread mainThread]);
    
    //1.獲得全局的并發隊列
    dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //2.添加任務到隊列中,就可以執行任務
        //異步函數:具備開啟新線程的能力
        dispatch_async(queue, ^{
                NSLog(@"下載圖片1----%@",[NSThread currentThread]);
            sleep(3);
            });
        dispatch_async(queue, ^{
                NSLog(@"下載圖片2----%@",[NSThread currentThread]);
            sleep(2);
            });
        dispatch_async(queue, ^{
                NSLog(@"下載圖片2----%@",[NSThread currentThread]);
            });

輸出:

2017-01-06 10:18:12.210 LZGCDTest[3134:63076] 主線程----<NSThread: 0x60800006e700>{number = 1, name = main}
2017-01-06 10:18:12.211 LZGCDTest[3134:63122] 下載圖片1----<NSThread: 0x60000007c040>{number = 3, name = (null)}
2017-01-06 10:18:12.211 LZGCDTest[3134:63121] 下載圖片2----<NSThread: 0x6080002671c0>{number = 4, name = (null)}
2017-01-06 10:18:12.211 LZGCDTest[3134:63124] 下載圖片2----<NSThread: 0x60800026fe80>{number = 5, name = (null)}

可以看出, 這里開啟了三個子線程來執行任務.
以上四個測試, 能夠看出同步, 異步, 串行, 并行之間的關系, 只有異步函數和并行隊列組合才能真正實現并發的效果.

測試五. 控制最大并發數

在進行并發操作的時候, 如果任務過多, 開啟很多線程, 會導致APP卡死. 所以, 我們要控制最大并發數, 這就用到了信號量, 與之相關的三個函數為:

// value : 必須是大于等于0的數, 否則會返回NULL
dispatch_semaphore_t
dispatch_semaphore_create(long value);

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

這里有一篇文章是介紹信號量的, 可參考:關于dispatch_semaphore的使用
一個應用示例:

dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 設置最大任務數
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);

    for (int i = 0; i < 100; i++) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_group_async(group, queue, ^{
           // 執行任務
            NSLog(@"%d", i);
            // 模擬任務時間, 休眠2s
            sleep(2);
            
            dispatch_semaphore_signal(semaphore);
        });
    }
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

這樣就會, 每十個一組的進行輸出...

四. 一些應用

下面給出一些, GCD在編程中的常用示例:

1. 只執行一次
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });

這個同常用來創建單例, 能夠保證block內的代碼只執行一次.

2. 延遲執行
dispatch_time_t time = dispatch_time ( DISPATCH_TIME_NOW , 3ull * NSEC_PER_SEC ) ;
    
    dispatch_after ( time , dispatch_get_main_queue (),^{
        
        NSLog ( @"waited at least three seconds." );
    });

這里是延遲3s來執行block內的任務;

3. 重復執行
dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
        NSLog(@"重復執行5次");
    });

這個方法可以重復執行某個任務, 這里執行5次輸出;

4. 指定執行

如果, 你想某個任務在其他任務執行之后再執行, 或者必須某個任務執行完,才能執行下面的任務, 可以使用這個函數dispatch_barrier_async :

dispatch_queue_t queue = dispatch_queue_create("LZQueueName", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch_async1");
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:4];
        NSLog(@"dispatch_async2");
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async");
        [NSThread sleepForTimeInterval:4];
        
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async3");   
    });

這里是使用的并發隊列異步執行, 按說應該是互不影響的, 但是有了** dispatch_barrier_async** ,他的作用就是在他前面的任務執行結束后它才執行,而且它后面的任務等它執行完成之后才會執行.
輸出:

2017-01-06 10:33:03.063 LZGCDTest[3454:71023] dispatch_async1
2017-01-06 10:33:05.004 LZGCDTest[3454:71026] dispatch_async2
2017-01-06 10:33:05.004 LZGCDTest[3454:71026] dispatch_barrier_async
2017-01-06 10:33:10.151 LZGCDTest[3454:71026] dispatch_async3
5. 匯總dispatch_group_async

dispatch_group_async可以實現監聽一組任務是否完成,完成后得到通知執行其他的操作。比如你執行三個下載任務,當三個任務都下載完成后你才通知界面做相應的刷新操作:

// 獲取全局隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 創建一個組
    dispatch_group_t group = dispatch_group_create();
    // 添加任務
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group1");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group2");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"updateUi");
    });

輸出:

2017-01-06 10:41:02.099 LZGCDTest[3668:76419] group2
2017-01-06 10:41:03.098 LZGCDTest[3668:76416] group3
2017-01-06 10:41:03.098 LZGCDTest[3668:76417] group1
2017-01-06 10:41:03.099 LZGCDTest[3668:76375] updateUi
6. 延長后臺運行時間

當用戶使用Home鍵使APP后臺運行, 一般只有最多5s的時間, 就會被系統殺死, 如果在這些時間里你不能完成一些操作, 例如清理緩存, 保存數據, 就有可能造成數據丟失, 這時可以使用這個方法, 來使程序的后臺運行時間延長至10分鐘左右:

// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;

// AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [self beingBackgroundUpdateTask];
    // 在這里加上你需要長久運行的代碼
    [self endBackgroundUpdateTask];
}

- (void)beingBackgroundUpdateTask
{
    self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [self endBackgroundUpdateTask];
    }];
}

- (void)endBackgroundUpdateTask
{
    [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
    self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容