iOS底層-25:GCD及使用

簡(jiǎn)介

GCD全稱Drand Central Dispatch

  • c語(yǔ)言,提供了非常多強(qiáng)大的函數(shù)

什么是GCD?

將任務(wù)添加到隊(duì)列,并且指定執(zhí)行任務(wù)的函數(shù)

GCD的優(yōu)勢(shì):

  1. GCD是蘋(píng)果公司為多核的并行運(yùn)算提供的解決方案。
  2. GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)
  3. GCD會(huì)自動(dòng)管理線程周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
    程序員只需要告訴GCD想執(zhí)行什么任務(wù),并不需要編寫(xiě)任何線程管理代碼

基礎(chǔ)寫(xiě)法

    //任務(wù)
    dispatch_block_t block = ^{
        NSLog(@"hello world");
    };
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);
    //異步函數(shù)
    dispatch_async(queue, block);

這就是將任務(wù)添加到隊(duì)列,并通過(guò)異步函數(shù)執(zhí)行。

  • 任務(wù)使用block封裝,任務(wù)的block沒(méi)有參數(shù)也沒(méi)有返回值。

  • 異步函數(shù) dispatch_async
    不用等待當(dāng)前語(yǔ)句執(zhí)行完畢,就可以執(zhí)行下一條語(yǔ)句

    • 會(huì)開(kāi)啟線程執(zhí)行block任務(wù),異步是多線程的代名詞
  • 同步函數(shù) dispatch_sync
    必須等到當(dāng)前語(yǔ)句執(zhí)行完畢,才可以執(zhí)行下一條語(yǔ)句

    • 不會(huì)開(kāi)啟線程,在當(dāng)前線程中執(zhí)行block任務(wù)

同步和異步耗能問(wèn)題

同步函數(shù)

    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    //同步函數(shù)
    dispatch_sync(queue, ^{
   
    });
    
    NSLog(@"%f",CFAbsoluteTimeGetCurrent() - time);

打印結(jié)果:

 0.000008

異步函數(shù)

    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    //異步函數(shù)
    dispatch_async(queue, ^{
//        NSLog(@"任務(wù)1");
    });
    
    NSLog(@"%f",CFAbsoluteTimeGetCurrent() - time);
 0.000027

空任務(wù)時(shí),同步函數(shù)耗時(shí)更少。

添加一個(gè)NSLog任務(wù)

  • 同步函數(shù)
    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    //同步函數(shù)
    dispatch_sync(queue, ^{
        NSLog(@"任務(wù)1");
    });
    
    NSLog(@"%f",CFAbsoluteTimeGetCurrent() - time);

打印結(jié)果

 任務(wù)1
 0.000099
  • 異步函數(shù)
    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    //異步步函數(shù)
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)1");
    });
    
    NSLog(@"%f",CFAbsoluteTimeGetCurrent() - time);

打印結(jié)果:

 0.000025
  任務(wù)1

一個(gè)簡(jiǎn)簡(jiǎn)單單的打印語(yǔ)句,同步函數(shù)耗費(fèi)時(shí)間遠(yuǎn)大于異步函數(shù)。

同步和異步函數(shù)都會(huì)耗費(fèi)時(shí)間,異步函數(shù)多用來(lái)解決并發(fā)和多線程問(wèn)題。

隊(duì)列

串行隊(duì)列:按照任務(wù)指派的順序來(lái)執(zhí)行任務(wù),前一個(gè)執(zhí)行完,下一個(gè)才能執(zhí)行
并行隊(duì)列:能夠同時(shí)執(zhí)行一個(gè)或多個(gè)任務(wù),執(zhí)行任務(wù)的順序不一定

切勿將串行隊(duì)列、并行對(duì)隊(duì)列和同步、異步混淆

函數(shù)與隊(duì)列搭配

函數(shù)與隊(duì)列搭配使用
同步函數(shù)與串行隊(duì)列
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);
    //同步函數(shù)
    
    dispatch_sync(queue, ^{
        NSLog(@"任務(wù)1");
        sleep(2);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"任務(wù)2");
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"任務(wù)3");
    });

打印結(jié)果:

 任務(wù)1
 任務(wù)2
 任務(wù)3
同步函數(shù)與并行隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //同步函數(shù)
    
    dispatch_sync(queue, ^{
        NSLog(@"任務(wù)1");
        sleep(2);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"任務(wù)2");
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"任務(wù)3");
    });

打印結(jié)果

 任務(wù)1
 任務(wù)2
 任務(wù)3
異步函數(shù)與串行隊(duì)列
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);
    //同步函數(shù)
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)1");
        sleep(2);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)3");
      
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)4");
      
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)5");
       
    });
異步函數(shù)與并行隊(duì)列
    //并行隊(duì)列 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //同步函數(shù)
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)1");
        sleep(2);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2");
        sleep(2);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)3");
        sleep(2);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)4");
        sleep(2);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)5");
        sleep(2);
    });

打印結(jié)果

任務(wù)2
任務(wù)1
任務(wù)3
任務(wù)4
任務(wù)5

面試題

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", DISPATCH_QUEUE_CONCURRENT);

    NSLog(@"任務(wù)1");
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2");
        dispatch_async(queue, ^{
            NSLog(@"任務(wù)3");
        });
        NSLog(@"任務(wù)4");
    });
    
    NSLog(@"任務(wù)5");   
}

異步函數(shù)和并發(fā)隊(duì)列不會(huì)阻塞線程。

打印結(jié)果:
任務(wù)1
任務(wù)5
任務(wù)2
任務(wù)4
任務(wù)3

  • 修改一個(gè)同步函數(shù)
    dispatch_queue_t queue = dispatch_queue_create("com.ly",   DISPATCH_QUEUE_CONCURRENT);

    NSLog(@"任務(wù)1");
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2");
        dispatch_sync(queue, ^{
            NSLog(@"任務(wù)3");
        });
        NSLog(@"任務(wù)4");
    });
    
    NSLog(@"任務(wù)5");

打印結(jié)果為:

任務(wù)1
任務(wù)5
任務(wù)2
任務(wù)3
任務(wù)4
  • 將隊(duì)列改為串行隊(duì)列
     //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.ly", NULL);

    NSLog(@"任務(wù)1");
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2");
        dispatch_sync(queue, ^{
            NSLog(@"任務(wù)3");
        });
        NSLog(@"任務(wù)4");
    });
    
    NSLog(@"任務(wù)5");

打印結(jié)果:

任務(wù)1
任務(wù)5
任務(wù)2
崩潰

面試題2:

    dispatch_queue_t queue = dispatch_queue_create("com.ly", DISPATCH_QUEUE_CONCURRENT);

    
    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    NSLog(@"0");
    dispatch_sync(queue, ^{
        NSLog(@"7");
    });
    dispatch_sync(queue, ^{
        NSLog(@"8");
    });
    dispatch_sync(queue, ^{
        NSLog(@"9");
    });

答案為:AC
3必須在0之前,0在789之前,123無(wú)序 789 無(wú)序

    //A. 1230789
    //B. 1237890
    //C. 3120798
    //D. 2137890 

主隊(duì)列與全局隊(duì)列

主隊(duì)列:

  • 專門用來(lái)在主線程上調(diào)度任務(wù)的串行隊(duì)列
  • 不會(huì)開(kāi)啟線程
  • 如果當(dāng)前主線程有任務(wù)正在執(zhí)行,那么無(wú)論主隊(duì)列中被添加了什么任務(wù),都不會(huì)被調(diào)度
    -dispatch_get_main_queue()

全局隊(duì)列

  • 為了方便程序員的使用,蘋(píng)果提供了全局隊(duì)列dispatch_get_global_queue(0, 0)
  • 全局隊(duì)列是一個(gè)并發(fā)隊(duì)列
  • 在使用多線程開(kāi)發(fā)時(shí),如果對(duì)隊(duì)列沒(méi)有特殊要求,在執(zhí)行異步任務(wù)的時(shí)候,可以直接使用全局隊(duì)列

死鎖現(xiàn)象
主線程因?yàn)槟阃胶瘮?shù)的原因先執(zhí)行任務(wù),主隊(duì)列等待著主線程中的任務(wù)執(zhí)行完畢,在執(zhí)行自己的任務(wù),主線程和主隊(duì)列相互等待會(huì)造成死鎖。

GCD用法

  1. 單例
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"創(chuàng)建單例");
    });
  1. 同步/異步函數(shù)
//同步函數(shù)
 dispatch_sync(queue, ^{
        NSLog(@"3-%@",[NSThread currentThread]);
    });
//異步函數(shù)
 dispatch_async(queue, ^{
        NSLog(@"4-%@",[NSThread currentThread]);
    });
  1. dispatch_source
  • CPU負(fù)荷小,盡量不占用系統(tǒng)資源
  • 聯(lián)結(jié)的優(yōu)勢(shì)
    在任一線程上調(diào)用它的一個(gè)函數(shù)dispatch_source_merge_data后,會(huì)執(zhí)行dispatch_source實(shí)現(xiàn)定義好的句柄(可以把句柄簡(jiǎn)單理解為一個(gè)block),這個(gè)過(guò)程叫做Custom event,用戶事件。是dispatch_source支持的一種事件。

句柄是一種指向指針的指針,它指向的就是一個(gè)類或者結(jié)構(gòu),它和系統(tǒng)有很密切的關(guān)系。HINSTANCE(實(shí)例句柄)、HBITMAP(位圖句柄)、HDC(設(shè)備表述句柄)、HICON(圖標(biāo)句柄)等。這其中還有一個(gè)通用句柄,就是HANDLE

dispatch_source_create 創(chuàng)建源
dispatch_source_set_event_handler 設(shè)置源事件回調(diào)
dispatch_source_merge_data 源事件設(shè)置數(shù)據(jù)
dispatch_source_get_data 獲取源事件數(shù)據(jù)
dispatch_resume 繼續(xù) 
dispatch_suspend 掛起

簡(jiǎn)單例子

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.totalComplete = 0;
    
    self.queue = dispatch_queue_create("ly.com", NULL);
    // 不依賴 runloop
    // 和下層內(nèi)核 kenel-workloop交互
    // DISPATCH_SOURCE_TYPE_TIMER 準(zhǔn)確
    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
    dispatch_source_set_event_handler(self.source, ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
        NSUInteger value = dispatch_source_get_data(self.source);
        self.totalComplete += value;
        NSLog(@"進(jìn)度: %.2f",self.totalComplete/100.0);
        self.progressView.progress = self.totalComplete/100.0;
    });
    
    self.isRunning = YES;
    dispatch_resume(self.source);
}



- (IBAction)didClickStartOrPauseAction:(id)sender {
   
    if (self.isRunning) {
        dispatch_suspend(self.source);
        dispatch_suspend(self.queue);
        NSLog(@"已經(jīng)暫停");
        self.isRunning = NO;
        [sender setTitle:@"暫停中.." forState:UIControlStateNormal];
    }else{
        dispatch_resume(self.source);
        dispatch_resume(self.queue);
        NSLog(@"已經(jīng)執(zhí)行了");
        self.isRunning = YES;
        [sender setTitle:@"執(zhí)行中.." forState:UIControlStateNormal];
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    NSLog(@"開(kāi)始了");
    
    for (int i= 0; i<100; i++) {
        dispatch_async(self.queue, ^{
            if (!self.isRunning) {
                NSLog(@"已經(jīng)暫停");
                return;
            }
            sleep(1);
            dispatch_source_merge_data(self.source, 1);
        });
    }
}
  1. 柵欄函數(shù)
    dispatch_async(queue, ^{
        NSLog(@"1");
        
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
        NSLog(@"2-%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"3");
    });
    dispatch_async(queue, ^{
        NSLog(@"4");
    });

1、2執(zhí)行完才會(huì)執(zhí)行3、4
柵欄函數(shù),堵塞的是隊(duì)列

  • 保護(hù)數(shù)據(jù)寫(xiě)入
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i<5000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            @synchronized (self) {
                [self.mArray addObject:image];
            }
        });
    }

多線程同時(shí)向數(shù)組中寫(xiě)入數(shù)據(jù)時(shí),可能會(huì)出現(xiàn)問(wèn)題,上面使用了同步鎖。也可以使用柵欄函數(shù)

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

    for (int i = 0; i<5000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            
            dispatch_barrier_async(concurrentQueue, ^{
                [self.mArray addObject:image];
            });
        });
    }
  • 柵欄函數(shù)不能使用全局隊(duì)列
    將上面代碼對(duì)列改為全局隊(duì)列,運(yùn)行會(huì)崩潰
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i<5000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            
            dispatch_barrier_async(concurrentQueue, ^{
                [self.mArray addObject:image];
            });
        });
    }

崩潰效果圖:


原因:
柵欄函數(shù)主要是攔截隊(duì)列,不讓隊(duì)列后面的任務(wù)執(zhí)行。而系統(tǒng)的全局隊(duì)列,不只是添加了我們寫(xiě)的任務(wù),系統(tǒng)也會(huì)使用這個(gè)隊(duì)列,添加任務(wù);被攔截才導(dǎo)致崩潰

  1. 調(diào)度組 dispatch_group_t
    __block int a, b;
    dispatch_queue_t queue = dispatch_queue_create("com.ly", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        a = 3;
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        b = 4;
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"%d",a+b);
    });
  • 進(jìn)組和出組必須成對(duì)存在
  • 只有當(dāng)group中的任務(wù)都執(zhí)行完畢才會(huì)走dispatch_group_notify里的方法
  • dispatch_group_async相當(dāng)于dispatch_group_enter()dispatch_group_leave()
  1. 延遲
  2. 線程通訊
  3. 信號(hào)量/鎖
    信號(hào)量的簡(jiǎn)單使用:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t sem = dispatch_semaphore_create(2);
    //任務(wù)1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"執(zhí)行任務(wù)1");
        sleep(1);
        NSLog(@"任務(wù)1完成");
        
    });
    
    //任務(wù)2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"執(zhí)行任務(wù)2");
        sleep(1);
        NSLog(@"任務(wù)2完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任務(wù)3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"執(zhí)行任務(wù)3");
        sleep(1);
        NSLog(@"任務(wù)3完成");
        dispatch_semaphore_signal(sem);
    });

信號(hào)量創(chuàng)建的大小只有2,所以1、2任務(wù)會(huì)一起執(zhí)行;執(zhí)行完畢之后才會(huì)執(zhí)行3。

GCD不像NSOperation可以直接控制并發(fā)數(shù),設(shè)置maxConcurrentOperationCount;它需要借助信號(hào)量來(lái)實(shí)現(xiàn)這一需求。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容