GCD 和NSOperationQueue

同步異步驹暑、并行串行的區(qū)分

  • 兩個容易搞混的概念:

  • 同步異步:是指任務添加到線程上這個過程的同步和異步

  • 串行并行:是指任務在線程上運行的串行和并行

  • 使用block,invocation或者是dispatch_function_t(C函數(shù))添加任務兼吓,任務中都是需要執(zhí)行return返回的视搏,同步異步就是指是否和這個return同步,如果是同步添加到線程上的任務筋遭,把任務添加到線程上的添加操作本身會等到任務中的return執(zhí)行之后才執(zhí)行自己的return。如果是異步添加到線程上的任務透且,添加任務操作本身執(zhí)行后會立即返回,并不會等待任務完成后自己才返回,例如如下這個bug:


- (void)viewDidLoad {

    [super viewDidLoad];



    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge void*)array;

    dispatch_async_f(queue, p, sumAB);

}

void sumAB(void *input){

    id object = (__bridge id)input;

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"線程結束");

}

  • 創(chuàng)建了一個線程(串行:并行串行不關緊要,只有一個任務最易,都是串行),聲明一個數(shù)組指針p傳給sumAB函數(shù)毕荐,添加到線程上,結果崩潰了BAD_ACCESS第美,原因如下:

  • dispatch_async_f執(zhí)行的是異步把任務添加到線程中慌闭,添加完成后立刻就返回了省古,這個時候任務還沒執(zhí)行,等到任務執(zhí)行的時候viewDidLoad函數(shù)已經(jīng)執(zhí)行完,p指針已經(jīng)被釋放了,就引起了BAD_ACCESS。相比之下如果改為同步添加:


- (void)viewDidLoad {

    [super viewDidLoad];



    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge void*)array;

    dispatch_sync_f(queue, p, sumAB);//修改了這里async->sync

}

void sumAB(void *input){

    id object = (__bridge id)input;

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"線程結束");

}

  • 運行后發(fā)現(xiàn)沒有報錯:原因如下:

  • dispatch_sync_f把任務同步添加到線程中,添加任務的操作要和任務保持同步返回,也就是說:把任務添加到線程上去還要等他執(zhí)行完才能返回盟蚣,所以黍析,dispatch_sync_f函數(shù)一直等待sumAB函數(shù)執(zhí)行,三秒鐘后屎开,sumAB執(zhí)行完奄抽,dispatch_sync_f才執(zhí)行完逞度,最后viewDidLoad才執(zhí)行結束馆匿,在sumAB執(zhí)行期間一直處在viewDidLoad的函數(shù)周期里面赃蛛,p沒有被釋放呕臂,所以不會崩潰破托。

  • 另外:如果一定要異步添加到線程上,需要保證p只能不被釋放掉诵闭,使用__bridge_retain和__bridge_transfer關鍵字轉交所有權瘟芝,如下:


- (void)viewDidLoad {

    [super viewDidLoad];



    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge_retained void*)array;//retain

    dispatch_async_f(queue, p, sumAB);

}

void sumAB(void *input){

    id object = (__bridge_transfer id)input;//transfer

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"線程結束");

}

  • P指針指向對象的所有權會轉交到sumAB的object手中易桃,直到sumAB執(zhí)行完之前該對象都不會被釋放。

  • 理解同步異步后并行和串行就容易理解了:


- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_SERIAL);//serial是串行

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge_retained void*)array;//retain

    dispatch_async_f(queue, p, sumAB);

    dispatch_async_f(queue, p, sumAB);

}

void sumAB(void *input){

    id object = (__bridge_transfer id)input;//transfer

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"任務結束");

}

  • 上面一共執(zhí)行了6秒鐘锌俱,3秒鐘和6秒鐘分別打印了任務結束晤郑。

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_queue_create("LivyNN", DISPATCH_QUEUE_ConCurrent);//concurrent并行

    NSArray *array = [[NSArray alloc] initWithObjects:@"liwei", nil];

    void *p = (__bridge_retained void*)array;//retain

    dispatch_async_f(queue, p, sumAB);

    dispatch_async_f(queue, p, sumAB);

}

void sumAB(void *input){

    id object = (__bridge_transfer id)input;//transfer

    [NSThread sleepForTimeInterval:3];

    NSLog(@"%@", object);

    NSLog(@"任務結束");

}

  • 上面一共執(zhí)行3秒鐘,第三秒鐘同時打印了兩個任務技術贸宏。

  • 另一方面:串行并行可以視為隊列本身的能力造寝,同步異步可以視為是調用者的調用方式:當調用者使用同步方式(sync)向異步(concurrent)線程中添加兩個任務時,線程有能力開辟多線程并行執(zhí)行兩個任務吭练,但是調用者同步添加說明他不需要線程這樣做诫龙,所以兩個任務是串行的;如果調用者異步(async)向一個串行(serial)線程添加兩個任務鲫咽,雖然調用者試圖讓兩個任務并發(fā)執(zhí)行签赃,但是線程能力有限,所以兩個任務還是串行執(zhí)行的分尸;所以锦聊,如果需要兩個任務并發(fā)執(zhí)行,需要異步地向并行線程中添加多個任務箩绍。

GCD

*創(chuàng)建一個線程:dispatch_queue_t queue = dispatch_queue_create("一個線程的名字孔庭,在調試的時候可以看到這個名字",DISPATCH_QUEUE_ConCurrent/DISPATCH_QUEUE_SERIAL);c是并行,s是串行材蛛。

  • 把一個任務添加到一個線程上去:提供block和void*函數(shù)指針兩種方法分別是(帶f的是void *圆到,不帶f的是block,帶a的是異步,不帶a的是同步):

*dispatch_async

*dispatch_async_f

*dispatch_sync

*dispatch_sync_f

  • 添加方法:

    dispatch_async(queue, ^{

        /*異步添加block*/

    });

    dispatch_async_f(queue, p, sumAB);//隊列,參數(shù),函數(shù) 



dispatch_sync(queue, ^{

        /*同步添加block*/

    });

    dispatch_sync_f(queue, p, sumAB);//隊列,參數(shù),函數(shù)仰税,注意函數(shù)要定義成C語言的形狀,p需要是void*類型的地址构资,如果需要傳id類型抽诉,則需要__bridge關鍵字轉化,int類型參數(shù)可以直接int a = 0;&a

  • 獲取到主隊列:

dispatch_queue_t mainQueue = dispatch_get_main_queue();

  • 獲取到全局隊列:

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

*其中:DISPATCH_QUEUE_PRIORITY_LOW是個枚舉陨簇,有:HIGH,DEFAULT,LOW,BACKGROUND,分別對應于2,0迹淌,-2河绽,MIN四個數(shù)字。

  • 第二個參數(shù)留待以后使用唉窃。

  • 通嘲沂危看到的(0,0)就是獲取到全局default優(yōu)先級的隊列。

注意注意:不能往主線程上加同步任務N品荨9豆颉廷痘!不能往主線程上加同步任務!<选笋额!不能往主線程上加同步任務!E窭兄猩!

  • 原因是同步任務的添加任務操作會等待任務執(zhí)行后才return,而同步添加的任務會等到主線程上當前的任務執(zhí)行完才會執(zhí)行后添加上去的任務鉴未,當前的任務是什么枢冤?就是添加任務操作。所以添加任務的任務要同步等待所添加的任務執(zhí)行完铜秆,所添加的任務又要等待主線程當前的任務執(zhí)行完淹真,當前的任務又是添加任務的任務。连茧。趟咆。。GG

  • 但是可以在其他線程里同步添加任務到主隊列:其他線程需要等主線程梅屉,主線程不需要等其他線程值纱。

*并行循環(huán)迭代:dispatch_apply:


    dispatch_queue_t concurrentQueue = dispatch_queue_create("livyNNTest", DISPATCH_QUEUE_CONCURRENT);

    dispatch_apply(100, concurrentQueue, ^(size_t size) {

        [NSThread sleepForTimeInterval:1.0];

        NSLog(@"complete");

    });

  • 注意這個方法是同步的,會阻塞線程坯汤,與正常的for循環(huán)一樣虐唠。且并不是完全并行的,如上面代碼惰聂,實測每秒鐘會同時執(zhí)行6-8次代碼塊疆偿;而不是100次。

*隊列掛起:dispatch_suspend(queue);當前正在執(zhí)行的任務不會中斷

*隊列恢復:dispatch_resume(queue);

  • 信號量(可以實現(xiàn)concurrent線程的最大并發(fā)數(shù)):

    dispatch_queue_t queue = dispatch_queue_create("liwei", DISPATCH_QUEUE_CONCURRENT);

    dispatch_semaphore_t signal = dispatch_semaphore_create(3);//創(chuàng)建信號量搓幌,初始有三個

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

        dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);//如果有可用的信號量就直接返回杆故,如果沒有就在這等

        [NSThread sleepForTimeInterval:1];

        NSLog(@"%d",i);

        dispatch_semaphore_signal(signal);//運行完了釋放信號量以循環(huán)使用

    });

  • 分組:

*創(chuàng)建分組:dispatch_group_t group = dispatch_group_create();

*把任務放進分組:dispatch_group_async/synx(group,queue,^{任務})

*等待分組內任務全部完成:dispatch_group_wait(group,FOREVER);第二個參數(shù)是最大等待時間,等不到就不阻塞了。

*示例:


    dispatch_queue_t queue = dispatch_queue_create("liwei", DISPATCH_QUEUE_CONCURRENT);

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, queue, ^{

        [NSThread sleepForTimeInterval:1.0];

        NSLog(@"第一個任務完成了");

    });

    dispatch_group_async(group, queue, ^{

        [NSThread sleepForTimeInterval:3.0];

        NSLog(@"第二個任務執(zhí)行完了");

    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        NSLog(@"分組中的任務都執(zhí)行完了");

    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

  • 1秒后第一個任務執(zhí)行完溉愁,再過兩秒处铛,后面兩句一起打印。

NSOperationQueue

*NSOperationQueue是基于GCD的封裝拐揭。

  • 創(chuàng)建一個

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSOperationQueue *queue = [NSOperationQueue mainQueue];

*添加block任務,相當于dispatch_async(queue,block):


[queue addOperationWithBlock:^{



}];

  • 創(chuàng)建任務撤蟆,添加到queue上

//block任務

    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{



    }];

    [queue addOperation:blockOperation];

//invocation任務

    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethod) object:nil];

    [queue addOperation:invocationOperation];

 //子類化NSOperation,覆蓋其main方法,得子類SubOperation

 SubOperation *subOperation = [[SubOperation alloc] initWith::::];

 [queue addOperation:subOperation]; 

 //添加一組

     NSArray *operationArray = @[blockOperation,invocationOperation];

    [queue addOperations:operationArray waitUntilFinished:NO]; //NO相當于async堂污,yes相當于sync

  • 注意:一個Operation只能被加到一個線程中家肯,且只能加一次;如果有很多類似的Operation可以使用子類化Operation的方式盟猖。

  • 添加依賴:


[blockOperation addDependency:invocationOperation];

  • 注意:如果invocationOperation從來沒有被添加到任何一個queue中過讨衣,blockOperation也永遠都不會被執(zhí)行换棚;另外,依賴可以跨越線程:

    [blockOperation addDependency:invocationOperation];

    [queue addOperation:invocationOperation];

    [mainQueue addOperation:blockOperation];

  • 這樣寫添加的依賴也是生效的反镇,blockOperation在主線程中一直處于等待狀態(tài)圃泡,知道blockOperation在queue中執(zhí)行完。

*Operation優(yōu)先級,代碼如下:


NSBlockOperation *blockOperation0 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation0");

    }];

    NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation1");

    }];

    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation2");

    }];

    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation3");

    }];

    NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"blockOperation4");

    }];

    blockOperation0.queuePriority = NSOperationQueuePriorityVeryLow;

    blockOperation1.queuePriority = NSOperationQueuePriorityLow;

    blockOperation2.queuePriority = NSOperationQueuePriorityNormal;

    blockOperation3.queuePriority = NSOperationQueuePriorityHigh;

    blockOperation4.queuePriority = NSOperationQueuePriorityVeryHigh;

    NSArray *operationArray = @[blockOperation1,blockOperation3,blockOperation2,blockOperation0,blockOperation4];

    [queue addOperations:operationArray waitUntilFinished:NO];

  • 輸出結果看到1-4都是隨機出現(xiàn)的愿险,這優(yōu)先級也沒用捌睦?不能夠辆亏,隨機出現(xiàn)是NSOperation在沒指定最大并發(fā)數(shù)的情況下默認是0风秤,無限的,如果這樣寫就可以清楚地看到4扮叨,3缤弦,2,1彻磁,0:

queue.maxConcurrentOperationCount = 1;

  • 上面這種情況下是串行隊列碍沐,所以先找優(yōu)先級高的執(zhí)行再找優(yōu)先級低的

queue.maxConcurrentOperationCount = 2;

  • 如果是這樣,就會4衷蜓,3或者3累提,4在前兩個;1磁浇,2或2斋陪,1在中間,最后執(zhí)行0

queue.maxConcurrentOperationCount = 3;

  • 如果是這樣置吓,就是4无虚,3,2在前面隨機排列衍锚,1友题,0在后面隨機排列

  • 什么也不寫的時候相當于:


queue.maxConcurrentOperationCount = MAX;

  • 意思就是能開多少子線程就開多少子線程,所以MAX>5的情況下戴质,就是0-4隨機排列度宦,這種情況下優(yōu)先級順序不生效,因為能力太強不用考慮誰優(yōu)先置森,大家都優(yōu)先斗埂,都優(yōu)先就隨機了符糊。

  • 取消線程:[operation cancel];

  • 取消隊列中的所有線程[operationQueue cancelAllOperations];

  • 掛起:[queue setSuspended:YES];

  • 恢復:[queue setSuspended:NO];

  • 等待某個操作:[operation waitUntilFinished];//注意:千萬不要在主線程中執(zhí)行這個

  • 等待queue中的操作都完成:[queue waitUntilAllOperationsAreFinished];

GCD唯一的有點就是速度快

NSOperationQueue有更多的依賴凫海,優(yōu)先級功能,且封裝性更好男娄。

結束

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末行贪,一起剝皮案震驚了整個濱河市漾稀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌建瘫,老刑警劉巖崭捍,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異啰脚,居然都是意外死亡殷蛇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門橄浓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粒梦,“玉大人,你說我怎么就攤上這事荸实≡让牵” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵准给,是天一觀的道長泄朴。 經(jīng)常有香客問我,道長露氮,這世上最難降的妖魔是什么祖灰? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮畔规,結果婚禮上夫植,老公的妹妹穿的比我還像新娘。我一直安慰自己油讯,他們只是感情好详民,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著陌兑,像睡著了一般沈跨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兔综,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天饿凛,我揣著相機與錄音,去河邊找鬼软驰。 笑死涧窒,一個胖子當著我的面吹牛,可吹牛的內容都是我干的锭亏。 我是一名探鬼主播纠吴,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慧瘤!你這毒婦竟也來了戴已?” 一聲冷哼從身側響起固该,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糖儡,沒想到半個月后伐坏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡握联,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年桦沉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片金闽。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡永部,死狀恐怖,靈堂內的尸體忽然破棺而出呐矾,到底是詐尸還是另有隱情苔埋,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布蜒犯,位于F島的核電站组橄,受9級特大地震影響,放射性物質發(fā)生泄漏罚随。R本人自食惡果不足惜玉工,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淘菩。 院中可真熱鬧遵班,春花似錦、人聲如沸潮改。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汇在。三九已至翰萨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間糕殉,已是汗流浹背亩鬼。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留阿蝶,地道東北人雳锋。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像羡洁,于是被迫代替她去往敵國和親玷过。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內容