GCD特殊函数使用

Posted WeaterMr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GCD特殊函数使用相关的知识,希望对你有一定的参考价值。

栅栏函数的使用

注意事项:
1.只针对于自己创建的并发队列起,如网络请求本身就在别的队列中在放到当前队列中,栅栏函数不起效。
2.如果用的是串行队列栅栏函数的作用等同于一个同步函数的作用,系统全局并发队列并不起效。
同步栅栏函数dispatch_barrier_sync(在主线程中执行):必须等到栅栏之前的任务执行完毕,才执行栅栏中的任务,并且会堵塞栅栏下面的任务。
异步栅栏函数dispatch_barrier_async:前面的任务执行完毕才会来到这里

- (void)demo2
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^

        NSLog(@"1");
    );
    dispatch_async(concurrentQueue, ^
        NSLog(@"2");
    );
    /* 2. 栅栏函数 */ // - dispatch_barrier_sync
    dispatch_barrier_async(concurrentQueue, ^
        sleep(5);

        NSLog(@"----栅栏%@-----",[NSThread currentThread]);
    );
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^
        NSLog(@"3");
    );
    // 4
    NSLog(@"4");
 


2021-08-12 11:20:42.644579+0800  [28005:4936975] 4
2021-08-12 11:20:42.644652+0800  [28005:4936993] 1
2021-08-12 11:20:42.644729+0800  [28005:4936989] 2
2021-08-12 11:20:47.650209+0800  [28005:4936989] 栅栏<NSThread: 0x2834e41c0>number = 5, name = (null)
2021-08-12 11:20:47.650806+0800  [28005:4936989] 3

通过打印结果可以总结,所谓的栅栏其实就相当于一张大的过滤网一样,并发队列就相当于一堵墙,当前面两个管道的水流完,在开启下两个管道的水。
这里为什么先执行4?因为4就没有添加到当前的队列中,所以不起效。

获取系统的全局并发队列,栅栏不起效。

    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    // 这里是可以的额!
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^

        NSLog(@"1");
    );
    
    dispatch_async(concurrentQueue, ^
        NSLog(@"2");
    );
    /* 2. 栅栏函数 */ // - dispatch_barrier_sync
    dispatch_barrier_async(concurrentQueue, ^
        sleep(5);

        NSLog(@"----栅栏%@-----",[NSThread currentThread]);
    );
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^
        NSLog(@"3");
    );
    // 4
    NSLog(@"4");
2021-08-12 11:32:40.578252+0800  [28012:4940742] 4
2021-08-12 11:32:40.578325+0800  [28012:4940754] 1
2021-08-12 11:32:40.578402+0800  [28012:4940752] 2
2021-08-12 11:32:40.578518+0800  [28012:4940753] 3
2021-08-12 11:32:45.583531+0800  [28012:4940754] ----栅栏<NSThread: 0x283059700>number = 6, name = (null)-----

原因:系统全局的并发队列可能对系统层次的资源进行调度,又因为,栅栏有类似堵塞的作用,所以会失效。

同步栅栏函数堵塞线程

- (void)demo2
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    // 这里是可以的额!
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^
        NSLog(@"1");
    );
    dispatch_async(concurrentQueue, ^
        NSLog(@"2");
    );
    /* 2. 栅栏函数 */ // - dispatch_barrier_sync
    dispatch_barrier_sync(concurrentQueue, ^
        sleep(5);
        NSLog(@"----栅栏%@-----",[NSThread currentThread]);
    );
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^
        NSLog(@"3");
    );
    // 4
    NSLog(@"4");
 

2021-08-12 13:36:39.012807+0800  [28226:4973408] 2
2021-08-12 13:36:39.012804+0800  [28226:4973411] 1
2021-08-12 13:36:44.014448+0800  [28226:4973286] ----栅栏<NSThread: 0x281174980>number = 1, name = main-----
2021-08-12 13:36:44.015104+0800  [28226:4973286] 4
2021-08-12 13:36:44.015175+0800  [28226:4973407] 3

总结:这个堵塞将影响到不再当前队列中的任务。

可变线程安全问题

- (void)demo3
    // 可变数组线程安全?
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    // 多线程 操作marray
    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];
                 [self.mArray addObject:image];
        );
    

解决方案:添加栅栏函数或互斥锁

           dispatch_barrier_async(concurrentQueue , ^
                [self.mArray addObject:image];
           );
@synchronized (self) 
               [self.mArray addObject:image];
            ;

信号量 针对并发队列

信号量的作用一般是用来使任务同步执行,类似于互斥锁,用户可以根据需要控制GCD最大并发数,来控制最大能同时执行多少任务。
基本使用

//信号量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(sem);
  • 这里的sem即最大开辟线程数量,最多可以执行多少个任务。
  • dispatch_semaphore_create 主要就是初始化信号量
  • dispatch_semaphore_wait是对信号量的value进行–,即加锁操作
  • dispatch_semaphore_signal 是对信号量的value进行++,即解锁操作
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_queue_create("111", DISPATCH_QUEUE_CONCURRENT);

    //任务1
 

    dispatch_async(queue, ^
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待

        NSLog(@"执行任务1");
        NSLog(@"任务1完成");
    );
    
    //任务2
    dispatch_async(queue, ^
        sleep(2);

        NSLog(@"执行任务2");
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem); // 发信号
    );
2021-08-12 17:12:39.931461+0800 006---GCD最大并发数[28746:5069779] 执行任务2
2021-08-12 17:12:39.931967+0800 006---GCD最大并发数[28746:5069779] 任务2完成
2021-08-12 17:12:39.932239+0800 006---GCD最大并发数[28746:5069780] 执行任务1
2021-08-12 17:12:39.932483+0800 006---GCD最大并发数[28746:5069780] 任务1完成

调度组

dispatch_group_create 创建组 
dispatch_group_async 进组任务 
dispatch_group_notify 进组任务执行完毕通知 dispatch_group_wait 进组任务执行等待时间


`常规用法`
```objectivec
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    // group 负责监控任务,queue 负责调度任务
    dispatch_group_async(group, q, ^
        sleep(1);
    NSLog(@"任务1 %@", [NSThread currentThread]);
    );

    dispatch_group_async(group, q, ^
    NSLog(@"任务2 %@", [NSThread currentThread]);
    );

    dispatch_group_async(group, q, ^
    NSLog(@"任务3 %@", [NSThread currentThread]);
    );

    // 监听所有任务完成 - 等到 group 中的所有任务执行完毕后,"由队列调度 block 中的任务异步执行"
    dispatch_group_notify(group, dispatch_get_main_queue(), ^
    // 修改为主队列,后台批量下载,结束后,主线程统一更新UI
    NSLog(@"OK %@", [NSThread currentThread]);
    );

    NSLog(@"come here");
GCD最大并发数[30010:5267216] come here
GCD最大并发数[30010:5267353] 任务3 <NSThread: 0x283d95440>number = 3, name = (null)
GCD最大并发数[30010:5267349] 任务2 <NSThread: 0x283dca240>number = 6, name = (null)
GCD最大并发数[30010:5267350] 任务1 <NSThread: 0x283dda240>number = 5, name = (null)
GCD最大并发数[30010:5267216] OK <NSThread: 0x283d90980>number = 1, name = main

进组和出组一般是成对使用的
dispatch_group_enter 进组
dispatch_group_leave 出组

```objectivec
   // 群组-统一监控一组任务
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    // 1> 入组 -> 之后的 block 会被 group 监听
    // dispatch_group_enter 一定和 dispatch_group_leave 要配对出现
    dispatch_group_enter(group);
    dispatch_async(q, ^
    NSLog(@"task1 %@", [NSThread currentThread]);
    // block 的末尾,所有任务执行完毕后,添加一个出组
    dispatch_group_leave(group);
    );

    //  再次入组
    dispatch_group_enter(group);
    dispatch_async(q, ^
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"task2 %@", [NSThread currentThread]);
    // block 的末尾,所有任务执行完毕后,添加一个出组
    dispatch_group_leave(group);

    );

    // 群组结束
    dispatch_group_notify(group, dispatch_get_main_queue(), ^
    NSLog(@"OVER");
    );
    NSLog(@"come here");

dispatch_source 使用

概念
dispatch_source是基础数据类型,用于协调特定底层系统事件的处理
dispatch_source替代了异步回调函数,来处理系统相关的事件,当配置一个dispatch时,你需要指定监测的事件、dispatch queue、以及处理事件的代码(block或函数)。当事件发生时,dispatch source会提交你的block或函数到指定的queue去执行

简单来说:这种事件是由你调用 dispatch_source_merge_data 函数来向自己发出的信号。

句柄是一种指向指针的指针,它指向的就是一个类或者结构,它和系统有密切的关系,这当中还有一个通用的句柄,就是HANDLE

实例句柄 HINSTANCE
位图句柄 HBITMAP
设备表句柄 HDC
图标句柄 HICON
特点优势
其CPU负荷非常小,基本不占用资源,联结的优势

dispatch_source_t source = dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)

案例

    //倒计时时间
    __block int timeout = 5;
    
    //创建队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    //创建timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);
    
    //设置1s触发一次,0s的误差
    /*
     - source 分派源
     - start 数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t,这是一个opaque类型,我们不能直接操作它。我们得需要 dispatch_time 和 dispatch_walltime 函数来创建它们。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。
     - interval 间隔时间
     - leeway 计时器触发的精准程度
     */
    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源
            dispatch_source_cancel(timer);
        else
            timeout--;
            dispatch_async(dispatch_get_main_queue(), ^
                //更新主界面的操作
                NSLog(@"倒计时 - %d", timeout);
            );
        
    );
    //开始执行dispatch源
    dispatch_resume(timer);

以上是关于GCD特殊函数使用的主要内容,如果未能解决你的问题,请参考以下文章

GCD栅栏函数dispatch_barrier

GCD中的dispatch_barrier_async函数的使用(栅栏函数)

iOS底层探索之多线程—GCD源码分析(栅栏函数)

iOS 多线程:『GCD』详尽总结

iOS底层探索之多线程—GCD源码分析( 信号量dispatch_semaphore_t)

P2731 骑马修栅栏 欧拉函数