iOS | 多线程(二) GCD

776 阅读9分钟

1. GCD 简介

  • 什么是GCD?

      全称是 Grand Central Dispatch
      纯 C 语言,提供了非常多强大的函数
    
  • GCD的优势

      GCD 是苹果公司为多核的并行运算提出的解决方案
      GCD 会自动利用更多的CPU内核(比如双核、四核)
      GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
      程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
    

利用函数把任务放入队列

2. 函数和队列

  • 线程同步,线程异步 和 串行队列,并发队列

  • 线程同步, @synchronized 多个线程同时访问会锁住,每次只允许执行一个任务,所以同步串行和同步并发都会造成堵塞。

  • 线程异步,不堵塞当前线程。

  • 串行队列:线程FIFO,排队

  • 并发队列:多个任务可以同时执行,异步并发

  • 异步并发才会使每个正在执行的任务都开辟一条新的线程

  • 异步串行只开辟了一条线程number = 6(主线程number = 1).

    2.1函数:

    异步 `dispatch_async`
      - 不用等待当前语句执行完毕,就可以执行下一条语句
      - 会开启线程执行 block 的任务
      - 异步是多线程的代名词
    同步 `dispatch_sync`
      - 必须等待当前语句执行完毕,才会执行下一条语句
      - 不会开启线程
      - 在当前执行 block 的任务
    

注意:异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。 具体例子: github.com/tanghaitao/…

image.png

个人理解,队列和线程 类似于 银行呼叫系统号码任务相当于处理事情怎么处理是银行呼叫系统控制的可以同时处理多个,也可以每次处理一个,甚至一会处理一个一会处理多个具体处理事情是通过取的号码来的,叫到谁,谁就去窗口

2.2 队列:

串行

image.png

并发

image.png

- (void)syncTest{
    
    //1:创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_SERIAL);
    //下面的方式也可以,但是用得少, DISPATCH_QUEUE_SERIAL 更加易懂
    //dispatch_queue_t queue = dispatch_queue_create("Cooci", NULL);
    
    //2:创建任务
    dispatch_block_t taskBlock = ^{
        NSLog(@"%@",[NSThread currentThread]);
    };
    //3:利用函数把任务放入队列
    dispatch_sync(queue, taskBlock);
    
}

2.2.1 串行同步队列:

串行同步队列 : FIFO: 先进先出

/**
 串行同步队列 : FIFO: 先进先出
 */
- (void)serialSyncTest{
    //1:创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i<20; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);//1-<NSThread: 0x6000001e4900>{number = 1, name = main}
        });
    }

}

image.png

2.2.2 串行异步队列:

 串行异步队列

 */
- (void)serialAsyncTest{
    //1:创建串行队列
    NSLog(@" %@",[NSThread currentThread]);//<NSThread: 0x6000024d41c0>{number = 1, name = main}
    dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i<20; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);//{number = 6, name = (null)}
        });
    }
    
    for (int i = 0; i<1000000; i++) {

    }
    
    NSLog(@"hello queue");
    
}

只开辟了一条线程number = 6(主线程number = 1).

image.png

2.2.3 同步并发队列:

堵塞直到之前的任务执行完成,因为是同一个队列同一个线程

/**
 同步并发 : 堵塞 同步锁  队列 : resume supend   线程 操作, 队列挂起 任务能否执行
 */
- (void)concurrentSyncTest{

    //1:创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<20; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    
    for (int i = 0; i<1000000; i++) {
 
    }
    NSLog(@"hello queue");
}

image.png

2.2.4 异步并发队列

异步并发: 有了异步函数不一定每个任务都会开辟线程,要异步并发才会,异步串行,只会开辟一条线程。

/**
 异步并发: 有了异步函数不一定每个任务都会开辟线程,要异步并发才会,异步串行,错的,同一个任务中使用一个新开辟的线程name = (null)
 */
- (void)concurrentAsyncTest{
    
    //1:创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<20; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
//            0-<NSThread: 0x600003dc5dc0>{number = 6, name = (null)}
//            1-<NSThread: 0x600003dc1000>{number = 5, name = (null)}
//            5-<NSThread: 0x600003d98700>{number = 4, name = (null)}
//            2-<NSThread: 0x600003d9cbc0>{number = 3, name = (null)}
        });
    }
    
//    for (int i = 0; i<1000000; i++) {
//
//    }
    
    NSLog(@"hello queue");
    
}

image.png

3. 函数与队列组合应用

下面代码:异步会开线程,消耗内存,执行会滞后

- (void)textDemo{
    
    dispatch_queue_t queue = dispatch_queue_create("haitao", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
    
    //  1 5 2 4 3
}

下面代码:同步阻塞当前线程和队列。

- (void)textDemo1{
    
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
    
    // 1 5 2 3 4
}

下面代码:同步阻塞当前线程,串行会FIFO,先进先出,

- (void)textDemo2{
    
    // 同步队列
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
    
    // 1 5 2
    
}

image.png

队列串行,nslog(‘2’),block,4,3
sync. nslog('2'),block,3,4
死锁

在viewDidLoad中执行下面的代码是会产生死锁。队列引起的循环等待

dispatch_sync(dispatch_get_main_queue(),^{
  [self doSomething];
});

在主队列中提交了两个任务,一个是viewdidload,一个是block任务,最后分配到主线程执行。当block中的方法调用完成之后,这个viewdidload方法中的执行才会向下走,因为先进队列的是viewdidload,viewdidload中又调用了block,所以block又进入了队列,viewdidload要执行完,需要先执行完block,block执行完需要等队列中viewdidload执行完,所以相互等待的死锁。

7648E5A7-3BBC-4EEE-9526-775D063679B0.png

主队列serial
专门用来在主线程上调度任务的队列,类似于串行队列
不会开启线程
如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
dispatch_get_main_queue();
全局队列concurrent
为了方便程序员的使用,苹果提供了全局队列 dispatch_get_global_queue(0, 0)
全局队列是一个并发队列
在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
    __block int a = 0;
//    dispatch_group_t t = dispatch_get_main_queue();
    // dispatch_get_global_queue : 并发队列
    while (a<5) { // 耗时足够长  ---  开辟线程能够调度回来  a++  线程不安全
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%@===%d",[NSThread currentThread],a);
            a++;
        });
    }

此时变量a是线程不安全的,如下图所示,===90,并不是5. 主线程上a也是线程不安全的,每次结果不是固定不变的数据

{number = 1, name = main}****7

{number = 1, name = main}****5

image.png

while循环内部调用 异步并发时,会开辟新的线程, 一开始a=0,进入异步并发队列后,a++,但是由于是一次执行了5个全局并发队列,这样会导致每次执行任务的时候在不同的线程中间做调度,来回重复执行多次,,导致a++执行了N次。 第一次a=0,a++,打印==1,发现第二个线程还没执行a++,又继续打印==1,然后某个线程a++执行完,此时另外一条线程也执行a++,打印==3,多个异步并发队列同时执行同一个任务就会产生很多问题,类似于同一任务需要不同部门(异步并发队列)同时调度。导致很多重复性的工作

解决线程不安全的办法一个异步并发队列执行多个相同的任务,异步并发内部加入while循环 但是这样的只能在一条线程中执行(number = 3),效率低。

dispatch_async(dispatch_get_global_queue(0, 0), ^{

        while (a<5) {//声明在里面才正常
            NSLog(@"%@===%d",[NSThread currentThread],a);
            a++;
        }
    });

image.png

4. GCD应用栅栏函数

4.1 背景:

在做网络请求时,我们会遇到一种情况,如下图所示,需要先请求完token再执行其他的网络请求,导致网络请求 嵌套,如果逻辑很复杂,就会导致阅读性差,不利于维护。 异步转同步,保证线程安全,如果只在一条线程中执行,如上面代码的while循环放到同一队列的情况,一次只能执行一个任务,Sync堵塞,效率就很低,同时无法监听任务执行完后的操作。

    [self requestToken:^(id value) {
        weakSelf.token = value;

        [weakSelf requestHeadDataWithToken:value handle:^(id value) {
            NSLog(@"%@",value);
            weakSelf.headData = value;
        }];

        [weakSelf requestListDataWithToken:value handle:^(id value) {
            NSLog(@"%@",value);
            weakSelf.listData = value;
        }];
    }];

初级解决方法; sys 堵塞 --- 1: 用户体验 2 : 异步,不知道什么时候请求完成,无法跟踪 //sync 堵塞等待,用户体验差

 dispatch_block_t task = ^{
        //堵塞等待,体验差
        dispatch_sync(queue, ^{
            [self requestToken:^(id value) {
                weakSelf.token = value;
            }];
        });
        //异步,不知道什么时候调用完成,想异步回调后执行其他任务
        dispatch_async(queue, ^{
            [weakSelf requestHeadDataWithToken:self.token handle:^(id value) {
                NSLog(@"%@",value);
                weakSelf.headData = value;
            }];
        });
        
        dispatch_async(queue, ^{
            [weakSelf requestListDataWithToken:self.token handle:^(id value) {
                NSLog(@"%@",value);
                weakSelf.listData = value;
            }];
        });
    };
    
    dispatch_async(queue, task);
 
    NSLog(@"请求完毕了?我要去其他事情了");

此时 栅栏函数 dispatch_barrier_async 就诞生了

4.2 特点:

  1. 顺序执行
  2. 线程安全
for (int i = 0; i<1000; 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];
            NSLog(@"%zd --- %@ ---- %d",self.mArray.count,[NSThread currentThread],i);
//            dispatch_barrier_async(concurrentQueue, ^{//async阻塞队列,sync阻塞队列和线程
                [self.mArray addObject:image];
                NSLog(@"%zd ===== %@ ====== %d",self.mArray.count,[NSThread currentThread],i);
//            });
//            @synchronized(self){
//               [self.mArray addObject:image];
//            }
            if (i==199) {
                NSLog(@"++++++++:%zd",self.mArray.count);
            }
        });
    }

image.png

没有添加栅栏函数的情况下: 数组个数不正常,不是固定的10000个。 image.png

添加栅栏函数的情况:

    [self.mArray removeAllObjects];
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("haitao", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);

    // signal
    for (int i = 0; i<10000; 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];
            NSLog(@"%zd --- %@ ---- %d",self.mArray.count,[NSThread currentThread],i);
          dispatch_barrier_async(concurrentQueue, ^{//async阻塞队列,sync阻塞队列和线程
                [self.mArray addObject:image];
                NSLog(@"%zd ===== %@ ====== %d",self.mArray.count,[NSThread currentThread],i);
          });
//            @synchronized(self){
//               [self.mArray addObject:image];
//            }
            if (i==199) {
                NSLog(@"++++++++:%zd",self.mArray.count);
            }
        });
    }

[self.mArray removeAllObjects];

image.png

栅栏函数dispatch_barrier_async保证了每次只调度一个任务的执行,只有一个线程(number = 64, name = (null)} ======)。保证了线程安全,类似于sync函数,但是栅栏函数比sync函数更好的地方是它不会堵塞当前线程

image.png

4.3 栅栏函数的3个缺点:

4.3.1 不能保证FIFO

栅栏函数dispatch_barrier_async但是不能保证执行的顺序是串行的FIFO,因为任务调度是并发的,任务开始和任务结束的时间不可控:

从下面的打印可以看出来,

先执行任务: 102 111 104 130 131

栅栏函数执行后的任务: 104 131 111 102 130

 55 ===== <NSThread: 0x600001e958c0>{number = 5, name = (null)} ====== 104
 56 ===== <NSThread: 0x600001e958c0>{number = 5, name = (null)} ====== 131
 57 ===== <NSThread: 0x600001e958c0>{number = 5, name = (null)} ====== 111
 58 ===== <NSThread: 0x600001e958c0>{number = 5, name = (null)} ====== 102
 59 ===== <NSThread: 0x600001e958c0>{number = 5, name = (null)} ====== 130
 0 --- <NSThread: 0x600001e92380>{number = 81, name = (null)} ---- 102
 0 --- <NSThread: 0x600001eb4900>{number = 46, name = (null)} ---- 111
 0 --- <NSThread: 0x600001ea0380>{number = 42, name = (null)} ---- 105
 0 --- <NSThread: 0x600001e83740>{number = 82, name = (null)} ---- 103
 0 --- <NSThread: 0x600001eb9e00>{number = 43, name = (null)} ---- 104
 0 --- <NSThread: 0x600001ebb840>{number = 72, name = (null)} ---- 113
 0 --- <NSThread: 0x600001e85b40>{number = 79, name = (null)} ---- 115
 0 --- <NSThread: 0x600001e48700>{number = 75, name = (null)} ---- 116
 0 --- <NSThread: 0x600001eba800>{number = 77, name = (null)} ---- 112
 0 --- <NSThread: 0x600001e92300>{number = 78, name = (null)} ---- 117
 0 --- <NSThread: 0x600001efeb80>{number = 73, name = (null)} ---- 114
 0 --- <NSThread: 0x600001eba4c0>{number = 74, name = (null)} ---- 118
 0 --- <NSThread: 0x600001e48700>{number = 75, name = (null)} ---- 119
 0 --- <NSThread: 0x600001e85b40>{number = 79, name = (null)} ---- 120
 0 --- <NSThread: 0x600001ebb840>{number = 72, name = (null)} ---- 121
 0 --- <NSThread: 0x600001e48700>{number = 75, name = (null)} ---- 122
 0 --- <NSThread: 0x600001e85b40>{number = 79, name = (null)} ---- 123
 0 --- <NSThread: 0x600001ebb840>{number = 72, name = (null)} ---- 124
 0 --- <NSThread: 0x600001e48700>{number = 75, name = (null)} ---- 125
 0 --- <NSThread: 0x600001eba4c0>{number = 74, name = (null)} ---- 126
 0 --- <NSThread: 0x600001efeb80>{number = 73, name = (null)} ---- 127
 0 --- <NSThread: 0x600001ebb840>{number = 72, name = (null)} ---- 128
 0 --- <NSThread: 0x600001e92300>{number = 78, name = (null)} ---- 129
 0 --- <NSThread: 0x600001eba700>{number = 80, name = (null)} ---- 130
 0 --- <NSThread: 0x600001ea0380>{number = 42, name = (null)} ---- 131
 0 --- <NSThread: 0x600001e83740>{number = 82, name = (null)} ---- 132
 0 --- <NSThread: 0x600001eba800>{number = 77, name = (null)} ---- 133
 0 --- <NSThread: 0x600001eb9e00>{number = 43, name = (null)} ---- 134

4.3.2 并不能保证所有任务执行完后执行

栅栏函数dispatch_barrier_async并不能保证所有任务执行完后执行dispatch_barrier_async里面的函数。 image.png

4.3.3 afn网络请求不能保证顺序执行

栅栏函数dispatch_barrier_async 是否可以放到网络请求中,保证顺序执行,都在同一个线程中? 答案是NO。 所有任务都是同一队列才可以。

//加载完毕了 栅栏函数上, afn异步请求的bug,是队列不统一,自定义队列和回调返回的队列不是同一个。

自定义队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
  dispatch_barrier_async(concurrentQueue, ^{
  
  //但是afn的队列是 
  af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
  
 这是两个不同的队列,这样就无法不能保证任务的顺序执行。

5. GCD应用调度组

5.1 背景

#栅栏函数dispatch_barrier_async的第2、3个bug引出了调度组 即使队列不同,但是group是同一个,这样就能保证任务的顺序执行。

//创建调度组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
    // 任务1
    })
    
    dispatch_group_async(group, queue, ^{
    // 任务2
    })
    
     dispatch_group_notify(group, dispatch_get_main_queue(), ^{
      // 任务3
      //执行完 任务1 任务2 以后 再执行 任务3
     })

如果是网络请求的话, 个人推荐使用YTKNetwork, 多个请求同时调用,写法特别简洁。

 YKBLoginSendMessageAPI *loginSendAPI = [YKBLoginSendMessageAPI new];
    YTKBatchRequest *batchRequest = [[YTKBatchRequest alloc] initWithRequestArray:@[recommendAPI, adlistAPI,hotArticeAPI,loginSendAPI]];
    
    kWeakSelf(self);
       [MBProgressHUD showActivityMessageInWindow:nil];
       [batchRequest startWithCompletionBlockWithSuccess:^(YTKBatchRequest * _Nonnull batchRequest) {
           [MBProgressHUD hideHUD];
           [weakself.tableView.mj_header endRefreshing];
           [weakself recommendAPISuccess:(YKBCardRecommendAPI *)batchRequest.requestArray[0]];
           [weakself adlistAPISuccess:(YKBAdListAPI *)batchRequest.requestArray[1]];
           [weakself hotArticeAPiSuuccess:(YKBQuoteArticeListCheckApi *)batchRequest.requestArray[2]];
           
       } failure:^(YTKBatchRequest * _Nonnull batchRequest) {
           [weakself.tableView.mj_header endRefreshing];
           [MBProgressHUD hideHUD];
           [MBProgressHUD showErrorMessage:@"请求失败,请稍后再试"];
       }];

5.2 延迟等待

long timeout = dispatch_group_wait(group, 0);

 if (timeout == 0) { notify }

5.3 enter - leave

 // 问题: 如果 dispatch_group_enter 多 dispatch_group_leave 不会调用通知
    // dispatch_group_enter 少 dispatch_group_leave  奔溃
    // 成对存在
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"第一个走完了");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"第二个走完了");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有任务完成,可以更新UI");
    });

6. GCD最大并发数

信号量 dispatch_semaphore_t 控制并发数的意义:可以防止程序的压力,内存消耗等问题。

// 同步
 dispatch_semaphore_t lock =  dispatch_semaphore_create(1);
        begin = CACurrentMediaTime();
        for (int i = 0; i < count; i++) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_signal(lock);
        }

默认SDWebImage的并发数是6,AFNetWorking的并发数是4。

//AFN最大图片下载任务4return [self initWithSessionManager:sessionManager
                 downloadPrioritization:AFImageDownloadPrioritizationFIFO
                 maximumActiveDownloads:4
                             imageCache:[[AFAutoPurgingImageCache alloc] init]];

具体代码: github.com/tanghaitao/…

  dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //信号量
    //总结:由于设定的信号值为3,先执行三个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过3
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@" --- %@ ---- 任务1",[NSThread currentThread]);
        NSLog(@"执行任务1");
        sleep(6);//【睡眠6秒】
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(semaphore);
    });
    
    //任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@" --- %@ ---- 任务2",[NSThread currentThread]);
        NSLog(@"执行任务2");
        sleep(5);// 【睡眠5秒】
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(semaphore);
    });
    
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@" --- %@ ---- 任务3",[NSThread currentThread]);
        NSLog(@"执行任务3");
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(semaphore);
    });
  --- <NSThread: 0x600003c9f740>{number = 5, name = (null)} ---- 任务2
  --- <NSThread: 0x600003cb4180>{number = 4, name = (null)} ---- 任务1
 执行任务2
 执行任务1
 任务2完成
  --- <NSThread: 0x600003c90d80>{number = 3, name = (null)} ---- 任务3
 执行任务3
 任务3完成
 任务1完成

分析:一次只执行2个任务,等这2个任务中有1个完成(任务2的睡眠时间短sleep(5)<sleep(6),任务2比任务1先完成)后,此时任务1还在睡眠,马上任务3开始执行,因为任务1还需要睡眠sleep(6-5),1秒钟,所以任务3先完成,接着任务1完成。

上面就是信号量的意义,类似于阀门,每次只允许几个任务执行,如果这几个任务开始执行了,只要有一个任务完成,下面的任务就可以接着执行。

如果dispatch_semaphore_create(1),就类似于串行的效果了。

7. Dispatch_source

应用场景: 倒计时,banner轮播图等等

{
self.queue = dispatch_queue_create("com.haitao", 0);
    
    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
    /**
     1: b创建source
     2: 绑定回调
     3: 触发函数
     */
    
    // runloop ---> source
    // 下面的函数是 调用封装source
    // 封装任务块 ---->  函数触发
//    dispatch_sync(_queue, ^{
//        
//    });
//    
    // 保存代码块 ---> 异步 dispatch_source_set_event_handler()
    // 封装我们需要回调的触发函数
    dispatch_source_set_event_handler(self.source, ^{
        
        NSUInteger value = dispatch_source_get_data(self.source);
        self.totalComplete += value;
        NSLog(@"进度:%.2f", self.totalComplete/100.0);
        self.progressView.progress = self.totalComplete/100.0;
    });
    
    self.isRunning     = YES;
    dispatch_resume(self.source);
    // resume (OC): dispatch_resume (c)
    // [task resume]
    
    - (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);
        });
    }
}
   


倒计时:

// GCD倒计时
- (void)startCoundown
{
    __block int timeout = showtime + 1; //倒计时时间 + 1
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
    dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0 * NSEC_PER_SEC, 0); //每秒执行
    dispatch_source_set_event_handler(_timer, ^{
        if(timeout <= 0){ //倒计时结束,关闭
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{
                
                [self dismiss];
                
            });
        }else{
            
            dispatch_async(dispatch_get_main_queue(), ^{
                [_countBtn setTitle:[NSString stringWithFormat:@"跳过%d",timeout] forState:UIControlStateNormal];
            });
            timeout--;
        }
    });
    dispatch_resume(_timer);
}