iOS 多線程 GCD part3:API

https://www.jianshu.com/p/072111f5889dios

 

2017.03.05 22:54* 字數 1667 閱讀 88評論 0喜歡 1git

0. 預備知識github

GCD對時間的描述有些新奇編程

#define NSEC_PER_SEC 1000000000ullswift

#define NSEC_PER_MSEC 1000000ull數組

#define USEC_PER_SEC 1000000ull安全

#define NSEC_PER_USEC 1000ullsession

MSEC:毫秒多線程

USEC:微秒app

NSEC:納秒

SEC:秒

 

NSEC_PER_SEC,每秒有多少納秒

USEC_PER_SEC,每秒有多少毫秒

 

#define NSEC_PER_SEC 1000000000ull //GCD最經常使用的時間描述

打如今開始日後5秒

double delayInSeconds = 5.0;

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));1->開始時間,2->延後時間

1. dispatch_group

1.1 dispatch_group_notify

當多個任務在並行隊列內無序的進行,須要在多個任務所有完成後馬上開啓新任務,那麼這時就是須要用到

dispatch_group_async與dispatch_group_notify的組合

    dispatch_queue_t coucurrent_queue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_CONCURRENT);

    

    dispatch_group_t group = dispatch_group_create();

    

    dispatch_group_async(group, coucurrent_queue, ^{

        for (int i = 0; i<10; i++) {

            NSLog(@"group-01 - %@", [NSThread currentThread]);

        }

    });

    

    dispatch_group_async(group, coucurrent_queue, ^{

        for (int i = 0; i<10; i++) {

            NSLog(@"group-02 - %@", [NSThread currentThread]);

        }

    });

    

    dispatch_group_notify(group, coucurrent_queue,^{

        NSLog(@"op finish,start new op");

    });

1.2 dispatch_group_wait

仍是1.1一樣的需求:當任務在並行隊列內無序的進行,須要在多個任務結束後馬上開啓新任務

也能夠用dispatch_group_async與dispatch_group_wait的組合來完成

    dispatch_queue_t coucurrent_queue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_CONCURRENT);

    

    dispatch_group_t group = dispatch_group_create();

    

    dispatch_group_async(group, coucurrent_queue, ^{

        for (int i = 0; i<10; i++) {

            NSLog(@"group-01 - %@", [NSThread currentThread]);

        }

    });

    

    dispatch_group_async(group, coucurrent_queue, ^{

        for (int i = 0; i<10; i++) {

            NSLog(@"group-02 - %@", [NSThread currentThread]);

        }

    });

    

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    NSLog(@"op finish");

只是須要注意的是dispatch_group_wait會阻塞當前線程,或者說GCD的全部帶_wait都會阻塞當前線程

dispatch_group_wait的第二參數爲超時時間,DISPATCH_TIME_FOREVER表明一直等下去.

也能夠將以上dispatch_group_wait代碼換成:

int delayInSeconds = 20;

dispatch_time_t cheak_Time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));

long res = dispatch_group_wait(group, cheak_Time);

if (res == 0) {

NSLog(@"zc done");

}else{

NSLog(@"zc ing");

}

設置超時時間爲20s,

1.group任務提早完成,提早返回

2.按指定時間返回

dispatch_group_wait的返回值爲0表明group的任務所有完成,不然就表明任務還在進行中

1.3 dispatch_async+dispatch_group_enter+dispatch_group_leave

    dispatch_group_t group = dispatch_group_create();

    NSMutableArray * array = [NSMutableArray array];

    for (int i=0; i<5000; i++) {

        dispatch_group_enter(group);//enter和leave成對出現

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            

            NSLog(@"add in %@", [NSThread currentThread]);

            

            [array addObject:[NSNumber numberWithInt:i]];

            dispatch_group_leave(group);//enter和leave成對出現

            

        });

    }

    NSLog(@"before wait %@", [NSThread currentThread]);

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

仍是1.1一樣的需求:當任務在並行隊列內無序的進行,須要在多個任務結束後馬上開啓新任務

也能夠用dispatch_async+dispatch_group_enter+dispatch_group_leaver的組合來完成

2.dispatch_after

double delayInSeconds = 5.0;

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));

dispatch_after(popTime, dispatch_get_main_queue(), ^{

    NSLog(@"zc after");

});

dispatch_after的用法如上方代碼,就很少說了

3.信號量dispatch_semaphore

首先介紹一下信號量(semaphore)的概念。信號量是持有計數的信號,不過這麼解釋等於沒解釋。咱們舉個生活中的例子來看看。

假設有一個房子,它對應進程的概念,房子裏的人就對應着線程。一個進程能夠包括多個線程。這個房子(進程)有不少資源,好比花園、客廳等,是全部人(線程)共享的。

可是有些地方,好比臥室,最多隻有兩我的能進去睡覺。怎麼辦呢,在臥室門口掛上兩把鑰匙。進去的人(線程)拿着鑰匙進去,沒有鑰匙就不能進去,出來的時候把鑰匙放回門口。

這時候,門口的鑰匙數量就稱爲信號量(Semaphore)。很明顯,信號量爲0時須要等待,信號量不爲零時,減去1並且不等待。

此段描述徹底摘自:bestswifer iOS多線程編程——GCD與NSOperation總結

3.1 多線程資源單一佔用

    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    dispatch_semaphore_t sem = dispatch_semaphore_create(1);

    

    NSMutableArray * muArr = [NSMutableArray array];

    

    for (int i = 0; i< 1000; i++) {

        dispatch_async(q, ^{

            

            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

            

            [muArr addObject:@(i)];

            NSLog(@"%d",i);

            

            dispatch_semaphore_signal(sem);

            

        });

    }

dispatch_semaphore_create,建立一個信號量爲1的信號

dispatch_semaphore_wait,只有信號量大於0才能經過dispatch_semaphore_wait,經過的同時信號量減1

dispatch_semaphore_signal,信號量加1

3.2 超時顯示

- (void)downloadFile

{

    _semaphore = dispatch_semaphore_create(0);

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://baobab.wdjcdn.com/14525705791193.mp4"] cachePolicy:1 timeoutInterval:30];

    [[self.session downloadTaskWithRequest:request] resume];

    

    double delayInSeconds = 5.0;

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));

    

    NSLog(@"zc wait");

    long res = dispatch_semaphore_wait(_semaphore, popTime);

    if (res) {

        NSLog(@"zc timed out");

    }else{

        NSLog(@"zc timed in");

    }

}

 

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

{

    dispatch_semaphore_signal(_semaphore);

    NSLog(@"zc done");

}

仍是那句全部GCD帶_wait函數都會阻塞當前線程

開始下載建立信號,

下載成功,增長信號量,

任務提早完成,提早返回

按指定時間返回

dispatch_semaphore_wait的返回值爲0表明任務完成,不然就表明任務還在進行中

3.3 dispatch_semaphore_wait超時時間的理解

overTime不是調用dispatch_semaphore_wait後等待的時間,而是信號量建立後的時間

-(void)overTimeTest{

    dispatch_semaphore_t signal = dispatch_semaphore_create(0);

    double delayInSeconds = 5.0;

    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));

    

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSLog(@"1 start wait");

        dispatch_semaphore_wait(signal, overTime);

        NSLog(@"須要線程同步的操做1 開始");

        dispatch_semaphore_signal(signal);

    });

    

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        sleep(3);

        NSLog(@"2 start wait");

        dispatch_semaphore_wait(signal, overTime);

        NSLog(@"須要線程同步的操做2");

        dispatch_semaphore_signal(signal);

    });

}

以上兩個block內的dispatch_semaphore_wait調用相差3秒,但執行dispatch_semaphore_wait倒是同時的.

4.dispatch_barrier_async

多個線程對內存中的數組或是字典進行讀的操做是OK,

但若是多個線程對內存中的數組或是字典進行讀+寫的操做,就會有問題

    NSMutableArray * muArr = [NSMutableArray array];

    [muArr addObject:@"1"];

    [muArr addObject:@"2"];

    [muArr addObject:@"3"];

    dispatch_queue_t con_queue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_CONCURRENT);

    

    dispatch_async(con_queue, ^{

        NSLog(@"%@",muArr);

    });

    

    dispatch_async(con_queue, ^{

        NSLog(@"%@",muArr);

    });

    

    dispatch_barrier_async(con_queue, ^{

        NSLog(@"add add add");

        [muArr addObject:@"4"];

    });

    

    dispatch_async(con_queue, ^{

        NSLog(@"%@",muArr);

    });

    

    dispatch_async(con_queue, ^{

        NSLog(@"%@",muArr);

    });

dispatch_barrier_async很好解決了多線程讀寫安全進行的問題,

在原有的多線程執行時,加入一個dispatch_barrier_async,

會像有柵欄同樣擋住多線程執行,而先執行dispatch_barrier_async,

在執行完dispatch_barrier_async以後,再繼續多線程的操做

5. dispatch_source

Dispatch Source用於監聽系統的底層對象,好比文件描述符,Mach端口,信號量等。主要處理的事件以下表

DISPATCH_SOURCE_TYPE_DATA_ADD   數據增長

DISPATCH_SOURCE_TYPE_DATA_OR    數據OR

DISPATCH_SOURCE_TYPE_MACH_SEND  Mach端口發送

DISPATCH_SOURCE_TYPE_MACH_RECV  Mach端口接收

DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 內存狀況

DISPATCH_SOURCE_TYPE_PROC   進程事件

DISPATCH_SOURCE_TYPE_READ   讀數據

DISPATCH_SOURCE_TYPE_SIGNAL 信號

DISPATCH_SOURCE_TYPE_TIMER  定時器

DISPATCH_SOURCE_TYPE_VNODE  文件系統變化

DISPATCH_SOURCE_TYPE_WRITE  文件寫入

方法

dispatch_source_create:建立dispatch source,建立後會處於掛起狀態進行事件接收,須要設置事件處理handler進行事件處理。

dispatch_source_set_event_handler:設置事件處理handler

dispatch_source_set_cancel_handler:事件取消handler,就是在dispatch source釋放前作些清理的事。

dispatch_source_cancel:關閉dispatch source,這樣後續觸發的事件時不去調用對應的事件處理handler,但已經在執行的handler不會被取消.

5.1 dispatch_source_set_timer

UITableView在被拖拽時NSTimer就不起做用了

必須加[[NSRunLoop currentRunLoop] addTimer:anyTimer forMode:NSRunLoopCommonModes];

而dispatch source timer與runloop是沒有關係的,因此能夠放心使用

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, DISPATCH_TARGET_QUEUE_DEFAULT);

 dispatch_source_set_event_handler(source, ^(){

  NSLog(@"sourceTimer Time log");

 });

 dispatch_source_set_timer(source, DISPATCH_TIME_NOW,5*NSEC_PER_SEC,1*NSEC_PER_SEC);//1->源,2->開始時間,3->間隔時間,4->偏差秒數

 _source = source;

 dispatch_resume(_source);

5.2 文件監聽

監視文件夾內文件變化

NSURL * directoryURL = [NSURL URLWithString:_path]; // assume this is set to a directory

 int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);

 if (fd < 0) {

  char buffer[80];

  strerror_r(errno, buffer, sizeof(buffer));

  NSLog(@"Unable to open \"%@\": %s (%d)", [directoryURL path], buffer, errno);

  return;

 }

 dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,

               DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);

 dispatch_source_set_event_handler(source, ^(){

  unsigned long const data = dispatch_source_get_data(source);

  if (data & DISPATCH_VNODE_WRITE) {

   NSLog(@"The directory changed.");

  }

  if (data & DISPATCH_VNODE_DELETE) {

   NSLog(@"The directory has been deleted.");

  }

 });

 dispatch_source_set_cancel_handler(source, ^(){

  close(fd);

 });

 _source = source;

 dispatch_resume(_source);

還要注意須要用DISPATCH_VNODE_DELETE去檢查監視的文件或文件夾是否被刪除,若是刪除了就中止監聽

6. dispathc_once

dispathc_once函數能夠確保某個 block 在應用程序執行的過程當中只被處理一次,並且它是線程安全的。因此單例模式能夠很簡單的實現,寫單例必備

+ (Manager *)sharedInstance {

    static Manager *sharedManagerInstance = nil;

    static dispatch_once_t once;

 

    dispatch_once($once, ^{

        sharedManagerInstance = [[Manager alloc] init];

    });

 

    return sharedManagerInstance;

}

7. dispathc_apply

7.1 並行隊列完成任務,開啓新任務

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(10, queue, ^(size_t index) {

    NSLog(@"%d\n",index);

});

NSLog(@"done");

打印結果爲:

2017-03-04 21:40:43.454 GCDTrain[1289:42894] 0

2017-03-04 21:40:43.454 GCDTrain[1289:42937] 3

2017-03-04 21:40:43.454 GCDTrain[1289:42940] 2

2017-03-04 21:40:43.454 GCDTrain[1289:42894] 4

2017-03-04 21:40:43.454 GCDTrain[1289:42894] 5

2017-03-04 21:40:43.454 GCDTrain[1289:42938] 1

2017-03-04 21:40:43.455 GCDTrain[1289:42894] 8

2017-03-04 21:40:43.454 GCDTrain[1289:42940] 6

2017-03-04 21:40:43.455 GCDTrain[1289:42937] 7

2017-03-04 21:40:43.455 GCDTrain[1289:42938] 9

2017-03-04 21:40:43.455 GCDTrain[1289:42894] done

因此這又是一種對多線程無序的限定的API,dispatch_applyblock內的全部任務被執行完以後纔會執行後面的代碼,固然dispatch_apply與前面提到的全部帶_wait的API同樣都是阻塞當前線程的

dispatch_apply與帶_wait的API也有不一樣,dispatch_apply比較適合作些重複的+執行次數肯定的任務

7.2 防止開啓線程過多

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for (int i = 0; i < 999; i++){

      dispatch_async(queue, ^{

         NSLog(@"%d,%@",i,[NSThread currentThread]);

      });

}

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(999, queue, ^(size_t i){

     NSLog(@"%d,%@",i,[NSThread currentThread]);

});

看兩份代碼的打印結果可知道,不用dispatch_apply的並行+異步會開啓許多線程,而咱們已經知道:「使用太多線程會致使消耗大量內存」,因此在這種場景下應該使用dispatch_apply

8.dispatch_set_target_queue

8.1dispatch_set_target_queue能夠設置queue的優先級

dispatch_queue_create建立隊列的優先級跟global dispatch queue的默認優先級同樣,假如咱們須要設置隊列的優先級,能夠經過dispatch_set_target_queue方法

dispatch_queue_t serialQueue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_SERIAL);  

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);  

dispatch_set_target_queue(serialQueue, globalQueue);

//這樣serialQueue的優先級和globalQueue的優先級同樣

8.2 定義隊列層級關係

將多個隊列用dispatch_set_target_queue設置爲某個串行隊列的下屬隊列,能夠防止並行執行

  • 加隊列層級關係

    dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

    dispatch_set_target_queue(queue1, targetQueue);//加層級關係

    dispatch_set_target_queue(queue2, targetQueue);//加層級關係

    dispatch_async(queue1, ^{

        NSLog(@"do job1");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job2");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job3");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job4");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job5");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job6");

    });

2017-03-04 22:07:40.723 GCDTrain[1400:51089] do job1

2017-03-04 22:07:40.723 GCDTrain[1400:51089] do job2

2017-03-04 22:07:40.724 GCDTrain[1400:51089] do job3

2017-03-04 22:07:40.724 GCDTrain[1400:51089] do job4

2017-03-04 22:07:40.724 GCDTrain[1400:51089] do job5

2017-03-04 22:07:40.725 GCDTrain[1400:51089] do job6

  • 不加隊列層級關係

    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue1, ^{

        NSLog(@"do job1");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job2");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job3");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job4");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job5");

    });

    dispatch_async(queue2, ^{

        NSLog(@"do job6");

    });

2017-03-04 22:01:04.781 GCDTrain[1365:48597] do job2

2017-03-04 22:01:04.781 GCDTrain[1365:48598] do job1

2017-03-04 22:01:04.781 GCDTrain[1365:48599] do job3

2017-03-04 22:01:04.781 GCDTrain[1365:48609] do job5

2017-03-04 22:01:04.781 GCDTrain[1365:48608] do job4

2017-03-04 22:01:04.782 GCDTrain[1365:48597] do job6

打印結果一目瞭然

9. dispatch IO

在書上,在別人的文章裏對dispatch IO都是一段引用蘋果官方的代碼,跑不起來,本身真會用了再說

10. 未完待續

還有不少API並未見過介紹,因此.....

文章參考:

Objective-C高級編程:iOS與OS X多線程和內存管理

bestswifer iOS多線程編程——GCD與NSOperation總結

戴銘 細說GCD如何用

 

 

寫在最前面:
轉自:https://blog.csdn.net/wpeng20125/article/details/73650569
對原文做了下排版利於理解,也感謝原文做者爲咱們說明這個函數

dispatch_time_t 類型,它的建立有兩個函數

    1. dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
      第一個參數是從什麼時間開始,通常直接傳
      DISPATCH_TIME_NOW表示從如今開始
      第二個參數表示具體的時間長度(不能直接傳 int 或 float), 能夠寫成這種形式 (int64_t)3* NSEC_PER_SEC
#define NSEC_PER_MSEC 1000000ull 每毫秒有1000000納秒 #define USEC_PER_SEC 1000000ull 每秒有1000000微秒 #define NSEC_PER_USEC 1000ull 每微秒有1000納秒 

注意 delta 的單位是納秒!
1秒的寫做方式能夠是 1* NSEC_PER_SEC; 1000* NSEC_PER_MSEC或 USEC_PER_SEC* NSEC_PER_USEC

  • 2.dispatch_walltime(<#const struct timespec * _Nullable when#>, <#int64_t delta#>),
    第一個參數是一個結構體, 建立的是一個絕對的時間點,好比 2016年10月10日8點30分30秒, 若是你不須要自某一個特定的時刻開始,能夠傳 NUll,表示自動獲取當前時區的當前時間做爲開始時刻, 第二參數意義同第一個函數
    dispatch_time_t time = dispatch_walltime(NULL, 5* NSEC_PER_SEC);

兩個函數的不一樣
例如:
從如今開始,1小時以後是觸發某個事件

使用第一個函數建立的是一個相對的時間,第一個參數開始時間參考的是當前系統的時鐘,當 device 進入休眠以後,系統的時鐘也會進入休眠狀態, 第一個函數一樣被掛起; 假如 device 在第一個函數開始執行後10分鐘進入了休眠狀態,那麼這個函數同時也會中止執行,當你再次喚醒 device 以後,該函數同時被喚醒,可是事件的觸發就變成了從喚醒 device 的時刻開始,1小時以後.

而第二個函數則不一樣,他建立的是一個絕對的時間點,一旦建立就表示從這個時間點開始,1小時以後觸發事件,假如 device 休眠了10分鐘,當再次喚醒 device 的時候,計算時間間隔的時間起點仍是,開始時就設置的那個時間點, 而不會受到 device 是否進入休眠影響

做者:zcc_ios 連接:https://www.jianshu.com/p/9e2c363a09e2 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索