OC底層原理(17)- GCD(柵欄,調度組,信號量,dispatch_source)

練習題數組

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block int a = 0;
    while (a < 5) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            a++;
        });
    }
    NSLog(@"a = %d", a);
}
複製代碼

A、 a = 0 B、a < 5 C、a = 5 D、a > 5安全

答案:C、Dbash

答案分析markdown

  • while 循環裏面的代碼是異步函數,而且整個代碼塊是一個耗時操做,因此在執行的時候並不會等到整個代碼執行完後再進入下一個循環,而是直接進入下一個循環
  • 每次執行一次代碼塊,由於是異步函數,因此每次都會新開啓一個線程來執行任務
  • a 的初始值爲 0,當還沒執行完 dispatch_async 時,a 都是爲 0 ,而且 a++ 會被執行不少次
  • 當 a++ 所在整個代碼被第一次執行完時,a = 1,當 a 的值被修改時,因爲 a 的地址只有一個,也就是修改的也是同一個內存空間,因此其餘線程的 a 的值也會被修改
  • 當 a 的值第一次大於 5 時,就會退出循環,但此時還有不少其餘線程任務的 a++ 還沒徹底執行完,因此在退出循環以後,a 的值還依舊會遞增
  • 因此 a 打印的結果就會大於等於 5 。

打印結果:併發

看到打印結果很明顯浪費了不少性能,想直接打到正確的結果仍是有必定的風險,要解決這風險咱們能夠嘗試一下使用信號量異步

//當value 等於 1 時,會起到鎖的效果,一次一個執行。
dispatch_semaphore_t sem = dispatch_semaphore_create(1);

// timeout = DISPATCH_TIME_FOREVER ,意味沒有回來,就一直等待,加鎖的效果
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

//解鎖
dispatch_semaphore_signal(sem);
複製代碼

優化代碼async

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //dispatch_semaphore_create(long value);當value 等於 1 時,會起到鎖的效果,一次一個執行。
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    
    __block int a = 0;
    while (a < 5) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"裏面 a = %d----%@", a, [NSThread currentThread]);
            a++;
            dispatch_semaphore_signal(sem);
        });
//            timeout = DISPATCH_TIME_FOREVER ,意味沒有回來,就一直等待
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//

    }
    NSLog(@"a = %d", a);
    
}
複製代碼

打印結果:函數

答案解析性能

  • 信號量的目的就是爲了每執行一次while 循環的時候,異步代碼塊也執行一次
  • 因爲是這是異步代碼塊,因此先回執行代碼塊後面的代碼,而後纔會開啓一個新線程執行塊裏面的代碼,當 a++ 執行完了纔是整個代碼塊執行的結束。
  • 因此在要執行代碼塊是便加上信號鎖,當整個代碼塊意味結束時,再解鎖,因而便不會消耗多餘的性能。

柵欄

dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
複製代碼

主要起到控制任務執行順序,起到同步效果優化

  • dispatch_barrier_async

    前面的任務執行完畢纔會到這裏

  • dispatch_barrier_sync

    做用相同,可是這個會堵塞線程,影響後面的任務執行

  • 注意,柵欄函數只能控制同一併發隊列

基本使用

- (void)demo2{
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("janice", DISPATCH_QUEUE_CONCURRENT);
    
    /* 1. 異步函數 */
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"123");
    });
    
    /* 2. 柵欄函數 */
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
    });
    
     /* 3. 異步函數 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"加載那麼多,喘口氣!!!");
    });
    
    NSLog(@"************起來幹!!");
}
複製代碼

打印結果:

  • 由於 1,2,3 都是異步函數,因此先打印了 ‘起來幹’
  • 由於 2 是柵欄函數,因此要等 1 執行玩了以後纔會執行 2 , 3 要等柵欄函數執行完了才能被執行。

實際使用場景一

- (void)demo3{
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    // signal -- 線程BUG
    for (int i = 0; i<2000; 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];
            [self.mArray addObject:image];
        });
    }
}
複製代碼

打印結果

有個問題,添加了2000張圖片,可是實際打印效果只有 1997,這是爲何呢,說明線程不安全。

優化辦法,在圖片加入到數組的時候,添加一個柵欄

- (void)demo3{
    
    // 順序執行
    // 線程安全
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    // signal -- 線程BUG
    for (int i = 0; i<2000; 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];
            });
        });
    }
}
複製代碼

打印結果:

就是添加一個柵欄來保障其線程安全

實際使用場景二

將自定義併發隊列換成全局併發隊列。

- (void)demo3{
    
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);

    // signal -- 線程BUG
    for (int i = 0; i<2000; 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];
            });
        });
    }
}

複製代碼

崩潰了,使用柵欄函數的時候,必定要注意,必定是自定義的併發隊列,全局隊列是整個系統都在用,在這了使用的情景會就會形成堵塞,因此就會崩潰

總結:

  • 柵欄函數可以起到同步效果
  • 柵欄函數還能起到安全性的效果
  • 堵塞隊列
  • 在堵塞隊列的時候要使用本身的隊列不要使用全局的
  • 使用同一個隊列,才能起到效果

調度組

做用:控制任務執行順序

  • dispatch_group_create 建立組
  • dispatch_group_async 進組任務
  • dispatch_group_notify 進組任務執行完畢通知
  • dispatch_group_wait 進組任務執行等待時間
  • dispatch_group_enter 進組
  • dispatch_group_leave 出組
  • 注意搭配使用

基本使用

- (void)groupDemo{
    
    //建立調度組
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue1 = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);

    // SIGNAL
    dispatch_group_async(group, queue, ^{
        NSString *logoStr = @"http://p.qpic.cn/qqcourse/QFzQYCgCrxlq7n5Jats36WGb4wxzJIYmFplnUUgdxk4gy66xicbmGCDM5DXDuVNQP/";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
    
    dispatch_group_async(group, queue1, ^{
        sleep(2);
        NSString *logoStr = @"https://www.baidu.com/img/baidu_resultlogo@2.png";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
    __block UIImage *newImage = nil;
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"數組個數:%ld",self.mArray.count);
        for (int i = 0; i<self.mArray.count; i++) {
            UIImage *waterImage = self.mArray[i];
            newImage = [KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
        }
        self.imageView.image = newImage;
    });

}

複製代碼

信號量

dispatch_semaphore_t
複製代碼
  • dispatch_semaphore_create 建立信號量
  • dispatch_semaphore_wait 信號量等待
  • dispatch_semaphore_signal 信號量釋放
  • 控制 GCD 併發數

基本使用

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 信號量 -- gcd控制併發數
    // 同步
    //總結:因爲設定的信號值爲3,先執行三個線程,等執行完一個,纔會繼續執行下一個,保證同一時間執行的線程數不超過3
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    //任務1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"執行任務1");
        sleep(1);
        NSLog(@"任務1完成");
        dispatch_semaphore_signal(semaphore);
    });
    
    //任務2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"執行任務2");
        sleep(1);
        NSLog(@"任務2完成");
        dispatch_semaphore_signal(semaphore);
    });
    
    //任務3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"執行任務3");
        sleep(1);
        NSLog(@"任務3完成");
        dispatch_semaphore_signal(semaphore);
    });
}
複製代碼

打印結果:

dispatch_source

  • 其 CPU 負荷很是小,儘可能不佔用資源
  • 聯結的優點

在任一線程上調用它的一個函數 dispatch_source_merge_data 後,會執行 Dispatch Source 實現定義好的句柄(能夠把句柄簡單理解爲一個 block)這個過程叫 Custom event ,用戶事件,是dispatch source 支持處理的一種事件。

句柄是一種指向指針的指針,它指向的就是一個類或者結構,它和系統有很密切的關係

HINSTANCE(實例句柄),HBITMAP(位圖句柄),HDC(設備表述句柄),HICON(圖標句柄)等。這當中還有一個通用的句柄,就是 HANDLE,好比下面的語句:

  • dispatch_source_create 建立源
  • dispatch_source_set_event_handler 設置源事件的回調
  • dispatch_source_merge_data 源事件設置數據
  • dispatch_source_get_data 獲取源事件數據
  • dispatch_resume 繼續
  • dispatch_suspend 掛起

基本使用

ViewController.m

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;

@property (nonatomic, assign) NSUInteger totalComplete;
@property (nonatomic) BOOL isRunning;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.totalComplete = 0;
    
    self.queue = dispatch_queue_create("com.tz.cn.janice", 0);
    
     第一個參數:dispatch_source_type_t type爲設置GCD源方法的類型,前面已經列舉過了。
     第二個參數:uintptr_t handle Apple的API介紹說,暫時沒有使用,傳0便可。
     第三個參數:unsigned long mask Apple的API介紹說,使用DISPATCH_TIMER_STRICT,會引發電量消耗加重,畢竟要求精確時間,因此通常傳0便可,視業務狀況而定。
     第四個參數:dispatch_queue_t _Nullable queue 隊列,將定時器事件處理的Block提交到哪一個隊列之上。能夠傳Null,默認爲全局隊列。注意:當提交到全局隊列的時候,時間處理的回調內,須要異步獲取UI線程,更新UI...不過這好像是常識,又囉嗦了...
     */
    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
    // 保存代碼塊 ---> 異步 dispatch_source_set_event_handler()
    // 設置取消回調 dispatch_source_set_cancel_handler(dispatch_source_t source,dispatch_block_t _Nullable handler)
    // 封裝咱們須要回調的觸發函數 -- 響應
    dispatch_source_set_event_handler(self.source, ^{
        
        NSUInteger value = dispatch_source_get_data(self.source); // 取回來值 1 響應式
        self.totalComplete += value;
        NSLog(@"進度:%.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);// mainqueue 掛起
        self.isRunning = NO;
        [sender setTitle:@"暫停中..." forState:UIControlStateNormal];
    }else{
        dispatch_resume(self.source);
        dispatch_resume(self.queue);
        self.isRunning = YES;
        [sender setTitle:@"加載中..." forState:UIControlStateNormal];
    }
    
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    NSLog(@"點擊開始加載");
    
    for (NSUInteger index = 0; index < 100; index++) {
        dispatch_async(self.queue, ^{
            if (!self.isRunning) {
                NSLog(@"暫停下載");
                return ;
            }
            sleep(2);

            dispatch_source_merge_data(self.source, 1); // source 值響應
        });
    }
}
複製代碼

打印結果:

相關文章
相關標籤/搜索