GCD函数与队列&其他详尽总结

简介

概念

1.全称是 Grand Central Dispatch,纯 C 语言,提供了非常多强大的函数

1.Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。
2.官方文档介绍:https://developer.apple.com/documentation/dispatch

2.将任务添加到队列,并且指定执行任务的函数

优势

1.GCD是apple为多核的并行运算提出的解决方案。
2.GCD能较好的利用CPU内核资源。
3.GCD不需要开发者去管理线程的生命周期。
4.使用简便,开发者只需要告诉GCD执行什么任务,并不需要编写任何线程管理代码。

任务和队列

任务

就是需要执行的操作,是 GCD 中放在 block 中在线程中执行的那段代码
先看一下下面的代码我们将其进行拆分,还原最基础的写法。

- (void)syncTest{
    // 把任务添加到队列 --> 函数
    // 任务 _t ref c对象
    dispatch_block_t block = ^{
        NSLog(@"hello GCD");
    };
    //串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
    // 函数
    dispatch_async(queue, block);
    
    // 函数 队列
    // 函数队列
    // block () - GCD 下层封装
    
}

再去想上面的总结【将任务添加到队列,并且指定执行任务的函数】然后我们就能明白 所谓的任务就是就是 block里的执行。
分析:

  • 任务 使用block封装
  • 任务 的block没有参数也没有返回值
函数

执行任务的函数,将任务放入队列中,下层进行任务与队列的关系处理。

  1. 异步函数
    dispatch_async(queue, block);
  • 不用等待当前语句执行完毕,就可以执行下一条语句。
  • 会开启线程执行block的任务(除了和主队列配合)
  • 异步是多线程的代名词
  1. 同步函数
    dispatch_sync(queue, block);
  • 必须等带当前语句执行完毕,才会执行下一条语句。
  • 不会开启线程
  • 在当前执行block的任务
队列

下面所示就是如何创建队列

  1. 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
  • 上面第二个参数 传NULL 或 DISPATCH_QUEUE_SERIAL 即为串行队列。因为它是一个宏 【 #define DISPATCH_QUEUE_SERIAL NULL】
  • 串行队列就可以想象为 一个,一次只能通过一辆汽车单行车道的山体隧道。 比较窄,同一时间内只能出来一辆车
  1. 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
  • 上面第二个传如 DISPATCH_QUEUE_CONCURRENT 即为并发队列。
  • 并发队列就可以想象为 一个, 多行车道可以通过多辆汽车的山体隧道。比较宽,同一时间可同时有多个车冒头。

除了上面的两种队列 还有下面两种较特殊的队列写法

  1. 主队列
 dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • 专门用来在主线程上调度任务的串行队列
  • 不会开启线程
  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度;
  1. 全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0)
  • 为了方便程序员的使用,苹果提供了全局队列 dispatch_get_global_queue(0, 0)
  • 全局队列是一个并发队列 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
截屏2020-11-05 下午6.03.34.png

注意:
1.任务资源有大有小(函数内部复杂度不同,执行耗时也不同)
2.队列不可执行任务,只是一个FIFO(先进先出)的存储容器,任务是由cpu调度的线程来执行
3.出队列不是立马执行,而是等待cpu调度到可执行的线程中

函数和队列组合

1、同步函数串行队列

- (void)serialSyncTest{
    //1:创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i<13; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
 }

运行:

函数与队列[7013:320867] 0-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 1-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 2-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 3-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 4-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 5-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 6-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 7-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 8-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 9-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 10-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 11-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 12-<NSThread: 0x6000020a8300>{number = 1, name = main}
  • 不会开启线程,在当前线程执行任务。
  • 任务串行执行,任务一个接着一个
  • 会产生堵塞

2、同步函数并发队列

///同步 +并发队列
-(void)concurrectSyncTest{
    //创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.hh.jj", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0;i<10; i++){
       ///同步函数
        dispatch_sync(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
 }

运行

函数与队列[7422:360463] 0-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 1-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 2-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 3-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 4-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 5-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 6-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 7-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 8-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 9-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] hello queue
  • 不会开启线程,在当前线程执行任务;
  • 任务一个接着一个

3、异步函数串行队列

///串行队列 + 异步函数
-(void)serialAsyncTest{
    //1.创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.hh.jj", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i<10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);

        });
    }
    NSLog(@"hello queue");
 }

运行

函数与队列[7529:367173] hello queue
函数与队列[7529:367346] 0-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 1-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 2-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 3-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 4-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 5-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 6-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 7-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 8-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 9-<NSThread: 0x600001136440>{number = 6, name = (null)}

  • 只会开启一条新线程
  • 任务顺序执行 一个 接一个。
  • 分析:这里可以看到主线程的任务( NSLog(@"hello queue");)并没有等待 在新线程的串行队列执行完而执行。这里不是绝对的,因为这里的同步函数是一条新的子线程上运行的任务 他们在串行队列中是有序的 。和主线程 不是一个线程他们之间的任务执行互不干扰 ,这里和任务的复杂度及cpu的调度有关,说白了 就是 hello queue 可能在 0前或者0 - 9 或 9 后 任意一个位置打印。

4、异步函数并发队列

///并发队列 + 异步函数
-(void)concurrentAsyncTest{
    ///1.创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.hh.jj", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<10 ; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);

        });
    }
    NSLog(@"hello queue");
}

运行

函数与队列[7666:379427] hello queue
函数与队列[7666:379501] 0-<NSThread: 0x6000008d1980>{number = 6, name = (null)}
函数与队列[7666:379506] 2-<NSThread: 0x6000008d5a80>{number = 3, name = (null)}
函数与队列[7666:379502] 3-<NSThread: 0x6000008d89c0>{number = 7, name = (null)}
函数与队列[7666:379503] 1-<NSThread: 0x6000008d5b40>{number = 4, name = (null)}
函数与队列[7666:379501] 4-<NSThread: 0x6000008d1980>{number = 6, name = (null)}
函数与队列[7666:379506] 5-<NSThread: 0x6000008d5a80>{number = 3, name = (null)}
函数与队列[7666:379505] 6-<NSThread: 0x6000008ae480>{number = 8, name = (null)}
函数与队列[7666:379502] 7-<NSThread: 0x6000008d89c0>{number = 7, name = (null)}
函数与队列[7666:379501] 8-<NSThread: 0x6000008d1980>{number = 6, name = (null)}
函数与队列[7666:379503] 9-<NSThread: 0x6000008d5b40>{number = 4, name = (null)}

  • 会开启多条线程,在每条新线程执行任务。
  • 任务异步执行,没有顺序,和CPU调度有关

5、同步函数主队列


2251862-60f29c3661eccc63.png
  • 会发生死锁。
  • 分析 :因为主队列也是串行队列 ,test05方法在串行队列中执行任务, 而此时同步函数 dispatch_sync 将任务block 放入到了 主队列中 ,而 串行队列 只能执行完一个在进行执行下一个,此时 block 等待着test05完成。而 test05又在等待着 block 任务完成才算自己完成任务。所以这就造成了死锁。

6、异步函数主队列。

/**
 主队列异步
 不会开线程,并且 永远优先执行 异步函数任务 之外的 任务无论任务多复杂。在顺序执行异步函数任务
 */
- (void)mainAsyncTest{
    dispatch_queue_t queue =   dispatch_get_main_queue();
    for (int i = 0; i<10; i++) {
         dispatch_async(queue, ^{
             
             NSLog(@"%d-%@",i,[NSThread currentThread]);
         });
     }
  
    NSLog(@"hello queue");
    NSLog(@"hello queue");
    NSLog(@"hello queue");
   
}

运行

函数与队列[8762:452443] hello queue
函数与队列[8762:452443] hello queue
函数与队列[8762:452443] hello queue
函数与队列[8762:452443] 0-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 1-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 2-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 3-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 4-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 5-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 6-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 7-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 8-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 9-<NSThread: 0x600002db0380>{number = 1, name = main}

  • 不会开启新线程。
  • 并且异步函数任务之外的任务 永远在其之前完成无论任务多么得复杂。
常见相关面试题

面试题 -1

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

分析

  • 首先当前任务量大小是为一致的前提分析此题答案
  • 当前是 一个 并发队列,此时并发队列 里填入了一个 异步函数任务,异步函数执行任务内又有一个异步函数;异步函数+上并发队列会开启新的线程,然后在看一下结构 NSLog(@"1"); 优先执行,然后打印 NSLog(@"5");然后外层异步函数在新线程上执行任务块block ,block任务 先执行NSLog(@"2") ,又来到 异步函数,异步函数会在此队列关系上会开启新线程,所以当前线程,将block任务丢给其他线程处理, 继续向后执行,NSLog(@"4"), 然后 其他线程上的任务block执行完毕。下面画个图可能更明白。


    面试-1.jpg
  • 由此我们得知答案为打印 15 2 4 3 可是真的是这样吗?其实不然。why?
    答: 一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入输出操作时,就会让出cpu并进入暂时中止状态,拿上面案例来说,如果 当前线程 在开启新线程1和抛出5数字之间线程发生了导致阻塞的事件,此时线程会让出cpu进入暂时中止状态。而 新线程1的任务在异步执行,运行到 NSLog函数将2抛出来了, 此时 当前线程阻塞事件解除,线程进入就绪状态,重新到就绪队列中排队。这时候被cpu调度选中之后会从原来停止的位置继续执行。并打印出5.所以此时为 为 12 5 ... ,可是它在哪个时间段解除阻塞,时机不确定,所以5的打印 有可能 在 3 后 ,4 后。 除了阻塞问题 跟 当前任务的大小也有关系,不过当前(任务量相当都是NSLog());

  • 在主线程调用 -(void)textDemo 运行 多试几次。在子线程 中调用 -(void)textDemo 运行并多试几次。 你就会发现上面所说的问题。

面试题 -2

- (void)wbinterDemo{//
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    // 1 2 3
    //  0 (7 8 9)
    dispatch_async(queue, ^{ // 耗时
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    
    // 堵塞哪一行
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    
    NSLog(@"0");

    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });

分析:首先是一个并发队列。并发队列可以同一时刻执行多个任务。就是比较宽。的数据结构。然后12任务的执行函数为异步。异步函数+并发队列可开启多条线程 。3任务的执行函数为同步,同步函数+并发队列不会开启新的线程。会阻塞当前线程,直到它自己的任务完成。 0在当前线程上。所以 3和0处在一条线程上,并且3一定在0 的前边。7 8 9 任务依旧是由异步函数执行的。所以会开启多线程。这里请注意并不是每写一个异步函数 +添加到队列 就会向对应得开启一条新的线程。下面画图表示

面试-2.jpg
  • 由此我们可以看出 此答案 为 3 一定在 0 的前面 。任务1 任务2 异步没顺序 任务7 8 9 异步没顺序。由于 12 并没有使用当前线程 。而3 是同步函数并发队列。还是走的当前线程。在某些情况下 3 也可能在 1 前面 或后面 或2 前面 或2 后面 所以 123 无序。但是一定不会跑到 789 区域。因为 789 的 线程是在 当前任务 0 完成之后 cpu 重新调度 或 开启新的线程的。
扩展 线程的5种状态

1、新生状态: 当我们用NSTherad构造方法创建一个新线程时。如

    NSThread * th = [[NSThread alloc]initWithTarget:self selector:@selector(wbinterDemo) object:nil];
  • 该线程就是创建状态,此时它已经有了相应的内存空间和其它资源,但是还没有开始执行。

2、就绪状态: 新建线程对象后,调用该线程的 start()方法就可以启动线程。

 [th start];
  • 新建线程对象后,调用该线程的 start()方法就可以启动线程。当线程启动时,线程进入就绪状态(runnable)。由于还没有分配CPU,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。当系统挑选一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态。系统挑选的动作称之为“CPU调度"。

3、运行状态: 当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。

4、阻塞状态:一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入输出操作时,将让出 CPU 并暂时中止自己的执行,进入堵塞状态。

  • 堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程转入就绪状态。重新到就绪队列中排队等待,这时被CPU调度选中后会从原来停止的位置开始继续执行。
  • 记住:阻塞被消除后是回到就绪状态,不是运行状态。

5、死亡状态: 线程主动取消,自然死亡。线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。(苹果主线程自动加入runloop,子线程需要手动加入)

GCD线程间的通讯

在开发中 平常在主线程刷新UI,如:滚动、点击、拖拽 事件等。为了不阻塞主线程。通常把耗时操作 放在子线程中去。例如下载图片。下载完了之后需要刷新UI进行展示。就用到了线程的通讯。

-(void)threadCommunication{
    ///获取全局并发队列
   dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   /// 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        ///做复杂的事情
        for (int i =0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];//模拟耗时操作
        }
        NSLog(@"复杂的事情做完了");
        ///回到主线程
        dispatch_async(mainQueue, ^{
            NSLog(@"回到主线程 ");
        });
     });
 }

运行

2020-11-10 20:41:25.030180+0800 GCD必备技能[36785:2454975] 复杂的事情做完了
2020-11-10 20:41:25.030416+0800 GCD必备技能[36785:2454855] 回到主线程 

dispatch_barrier_async 栅栏函数。

我们有时候异步执行两组任务。第一组任务完成之后,才能开始执行第二组的操作。这时候我们就需要将两组任务分割开。就用到了 栅栏函数。

-(void)barrier
{
    ///并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.barrier.test", DISPATCH_QUEUE_CONCURRENT);
    /// 1
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
        }
        
    });
    /// 2
    dispatch_async(queue, ^{
        
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
    });
    ///来个栅栏
    dispatch_barrier_async(queue, ^{
        
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
           
            NSLog(@"barrier---%@",[NSThread currentThread]);
            
        }
    });
    /// 􏱈3
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
    });
    /// 􏳂􏲭􏱇􏱈4
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"4---%@",[NSThread currentThread]);
        }
    });
 
}

运行

GCD必备技能[36994:2466531] 2---<NSThread: 0x600001e1f380>{number = 5, name = (null)}
GCD必备技能[36994:2466532] 1---<NSThread: 0x600001e27c80>{number = 3, name = (null)}
GCD必备技能[36994:2466532] 1---<NSThread: 0x600001e27c80>{number = 3, name = (null)}
GCD必备技能[36994:2466531] 2---<NSThread: 0x600001e1f380>{number = 5, name = (null)}
GCD必备技能[36994:2466531] barrier---<NSThread: 0x600001e1f380>{number = 5, name = (null)}
GCD必备技能[36994:2466531] barrier---<NSThread: 0x600001e1f380>{number = 5, name = (null)}
GCD必备技能[36994:2466529] 3---<NSThread: 0x600001e2bec0>{number = 4, name = (null)}
GCD必备技能[36994:2466531] 4---<NSThread: 0x600001e1f380>{number = 5, name = (null)}
GCD必备技能[36994:2466531] 4---<NSThread: 0x600001e1f380>{number = 5, name = (null)}
GCD必备技能[36994:2466529] 3---<NSThread: 0x600001e2bec0>{number = 4, name = (null)}

  • 看到这里我们知道了dispatch_barrier_async的用法及特性 。
  • 那栅栏函数还有一个方法 dispatch_barrier_sync 同步它是什么特性?或者说它和dispatch_barrier_async 的区别是什么?

我们将上述代码 中 dispatch_barrier_async 改为 dispatch_barrier_sync继续运行

GCD必备技能[37176:2475447] 2---<NSThread: 0x600002892e00>{number = 4, name = (null)}
GCD必备技能[37176:2475445] 1---<NSThread: 0x6000028dc000>{number = 6, name = (null)}
GCD必备技能[37176:2475447] 2---<NSThread: 0x600002892e00>{number = 4, name = (null)}
GCD必备技能[37176:2475445] 1---<NSThread: 0x6000028dc000>{number = 6, name = (null)}
GCD必备技能[37176:2475322] barrier---<NSThread: 0x60000289c180>{number = 1, name = main}
GCD必备技能[37176:2475322] barrier---<NSThread: 0x60000289c180>{number = 1, name = main}
GCD必备技能[37176:2475451] 4---<NSThread: 0x6000028d3a00>{number = 5, name = (null)}
GCD必备技能[37176:2475447] 3---<NSThread: 0x600002892e00>{number = 4, name = (null)}
GCD必备技能[37176:2475451] 4---<NSThread: 0x6000028d3a00>{number = 5, name = (null)}
GCD必备技能[37176:2475447] 3---<NSThread: 0x600002892e00>{number = 4, name = (null)}

  • 经过多方面的运行,在当前案例中并未发现有何区别。但是实际上是有区别的,仔细对比看栅栏函数任务执行的线程。你就会发现问题 此时同步栅栏 在当前线程执行任务 它的作用某些程度上 和 dispatch_sync同步函数是一样的。在 并发队列环境并不会开启新线程会在当前线程执行。它和dispatch_sync 唯一的区别是 同步栅栏 会分割上下任务。同步函数不会
  • 下面我们在当前线程再次执行一个任务。

在任务4后边添加一个 任务 前边和上面一样

-(void)barrier{
 ... 前面一摸摸一样样。
    /// 􏳂􏲭􏱇􏱈4
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"4---%@",[NSThread currentThread]);
        }
    });
    
    ///当前 线程添加个任务 搞复杂点
    [NSThread sleepForTimeInterval:2];
    NSLog(@"我是当前线程最后一个任务---%@",[NSThread currentThread]);
 
}

栅栏函数 为 dispatch_barrier_async 运行

 GCD必备技能[37681:2500605] 我是当前线程最后一个任务---<NSThread: 0x600002214080>{number = 1, name = main}
 GCD必备技能[37681:2500739] 2---<NSThread: 0x600002256180>{number = 3, name = (null)}
 GCD必备技能[37681:2500736] 1---<NSThread: 0x600002255540>{number = 7, name = (null)}
 GCD必备技能[37681:2500739] 2---<NSThread: 0x600002256180>{number = 3, name = (null)}
 GCD必备技能[37681:2500736] 1---<NSThread: 0x600002255540>{number = 7, name = (null)}
 GCD必备技能[37681:2500739] barrier---<NSThread: 0x600002256180>{number = 3, name = (null)}
 GCD必备技能[37681:2500739] barrier---<NSThread: 0x600002256180>{number = 3, name = (null)}
 GCD必备技能[37681:2500739] 3---<NSThread: 0x600002256180>{number = 3, name = (null)}
 GCD必备技能[37681:2500736] 4---<NSThread: 0x600002255540>{number = 7, name = (null)}
 GCD必备技能[37681:2500739] 3---<NSThread: 0x600002256180>{number = 3, name = (null)}
 GCD必备技能[37681:2500736] 4---<NSThread: 0x600002255540>{number = 7, name = (null)}
  • 此时看到 当前线程上的任务【我是当前线程最后一个任务】自己独占当前线程。并未与其他线程产生什么联系。所以它在哪里打印出来与它的任务复杂度有关。

栅栏函数 为 dispatch_barrier_sync 运行

GCD必备技能[37753:2503876] 2---<NSThread: 0x6000032bdd00>{number = 6, name = (null)}
GCD必备技能[37753:2503878] 1---<NSThread: 0x6000032f86c0>{number = 5, name = (null)}
GCD必备技能[37753:2503876] 2---<NSThread: 0x6000032bdd00>{number = 6, name = (null)}
GCD必备技能[37753:2503878] 1---<NSThread: 0x6000032f86c0>{number = 5, name = (null)}
GCD必备技能[37753:2503754] barrier---<NSThread: 0x6000032f0240>{number = 1, name = main}
GCD必备技能[37753:2503754] barrier---<NSThread: 0x6000032f0240>{number = 1, name = main}
GCD必备技能[37753:2503754] 我是当前线程最后一个任务---<NSThread: 0x6000032f0240>{number = 1, name = main}
GCD必备技能[37753:2503876] 4---<NSThread: 0x6000032bdd00>{number = 6, name = (null)}
GCD必备技能[37753:2503878] 3---<NSThread: 0x6000032f86c0>{number = 5, name = (null)}
GCD必备技能[37753:2503876] 4---<NSThread: 0x6000032bdd00>{number = 6, name = (null)}
GCD必备技能[37753:2503878] 3---<NSThread: 0x6000032f86c0>{number = 5, name = (null)}
  • 此时无论你运行800遍 最后一个任务 一定是在 同步栅栏函数任务完成之后 执行的。因为此时的栅栏函数并未开启或在新的线程上面执行任务,它是在当前线程来分割上下异步任务。而我们新加的任务 也在其当前线程。所以 一定是同步栅栏任务执行完毕 ,在执行 我们新加的任务。而我们新加的任务 和 栅栏下面的异步函数 谁先执行完并不确定,因为跟任务复杂度有关系。
栅栏函数总结
  • 栅栏函数如果不考虑当前线程的情况下 无论异步还是同步 效果都是一样的。都会分离 上下任务。上面任务全部完成之后 才会调用 栅栏函数任务。当栅栏函数任务全部完成之后 才会执行下面任务。
  • 当考虑到当前线程的时候 使用会有区别。异步栅栏使用的是新线程。同步栅栏使用的是当前线程。所以如果离开栅栏函数及队列相关区域 在下方 有任务。此时的任务 如是同步栅栏 一定会等 同步栅栏完成后 调用,无论同步栅栏任务多复杂。而 如果是异步栅栏 。此任务执行跟栅栏没有任何关系。没在一条线程。熟先熟快完成 要看任务复杂度,及任务所在线程状态的影响。
  • 同步栅栏 除了可以分离上下文 其作用和 同步函数 dispatch_sync 在队列配合上有异曲同工之处。所以 如果当前是主队列 并且当前线程是主线程。使用了同步栅栏函数一样的会死锁。
常见相关面试题

面试-1 【gcd如何多读单写?】
首先分析 题目的含义: 单读多写 的意思就是 可以多个读者同时去读数据,而在读的时候,不能去写数据,并且在写的过程中不能有其他写者去写,即读者之间是并发的,写者与读者 或其它写者之间是互斥的。

上代码:

#import "SAObject.h"
@interface SAObject ()
@property (nonatomic, strong) dispatch_queue_t dictQueue;//并发队列
@property (nonatomic, strong) NSMutableDictionary *dict;//可变字典
@end
 @implementation SAObject
- (instancetype)init
{
    self = [super init];
    if (self) {
        _dictQueue = dispatch_queue_create("com.hh.queue", DISPATCH_QUEUE_CONCURRENT);
        _dict = [NSMutableDictionary dictionary];
     }
    return self;
}
- (id)valueForKey:(NSString *)key {
    id __block obj;
    dispatch_sync(_dictQueue, ^{
        obj = [self.dict valueForKey:key];
    });
    return obj;
}
- (void)setObject:(id)obj forKey:(id<NSCopying>)key {
    //重点:dispatch_barrier_async(),异步栅栏调用;
    //只有提交到并行队列才有意义
    dispatch_barrier_async(_dictQueue, ^{
        [self.dict setObject:obj forKey:key];
    });
}

分析:
首先 先看 - (id)valueForKey:(NSString *)key

  • 里面是一个 同步函数 dispatch_sync 它 和并发队列的作用是什么? 不会开启新的线程 在当前线程执行任务。所以这里 无论是哪个线程来读取数据,它的读取 操作是在 当前线程完成。可以主线程,也可以子线程上读取。不会重新分配线程,你该是啥线程就是啥线程读。读与读之间可以是并发的。

然后 在分析 setObject:(id)obj forKey:(id<NSCopying>)key

  • 里边是一个异步栅栏函数+并发,它的作用就是 不管 是哪一条线程调用 。会使用新的线程来执行里面的任务 。 并且需要看当前栅栏使用的队列,前面有没有任务,因为需要在前面任务全部完成之后,才会走栅栏里面的任务。 此时此刻 如果和 - (id)valueForKey:(NSString *)key 同属一条线程调用。如 读 在前 那么 写一定在后。如在写的后边 又有一条线程来读,那么一定是在 写完成之后。这就是上面的概念 读与写之间是互斥的。写与写之间也是互斥。因为 你无论是在哪一条线程 来进行 写入。最后你发现 他们只是在同一条线程上做事情。

GCD延迟执行方法:dispatch_after

经常回遇到这样的需求: 在指定时间几秒之后完成某个任务。就可以用GCD dispatch_after函数实现。需要注意的是:dispatch_after 函数并不是在指定时间之后才开始执行处理。而是在指定时间之后将任务追加到主队列。严格来说这个时间并不是绝对准确。想要延迟执行任务,dispatch_after是很有效果的。

-(void)after
{
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"asyncMain---begin");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         /// 3秒之后异步追加任务代码到主队列
         NSLog(@"after---%@",[NSThread currentThread]);
    });
}

运行

2020-11-13 14:48:45.801939+0800 GCD必备技能[76617:3667321] currentThread---<NSThread: 0x600000c100c0>{number = 1, name = main}
2020-11-13 14:48:45.802164+0800 GCD必备技能[76617:3667321] asyncMain---begin
2020-11-13 14:48:49.096429+0800 GCD必备技能[76617:3667321] after---<NSThread: 0x600000c100c0>{number = 1, name = main}

  • 请看打印的时间线。你会发现在 begin到 after之间 相差了4秒 所以说几秒之后执行block任务并不是绝对准确。

GCD 一次性代码(只执行一次):dispatch_once

创建单例。或者整个程序运行中只让执行一次的代码。我们就用到了dispatch_once
dispatch_once 函数能保证某段代码在程序运行过程中只被执行一次。并且在多线程环境下,dispatch_once也能保证线程安全。

   +(instancetype)sharedInstance
    {
        static dispatch_once_t once = 0;
        static id sharedInstance = nil;
        dispatch_once(&once, ^{
            //只实例化一次 默认线程安全
            sharedInstance = [[self alloc]init];
    
        });
        return sharedInstance;
     }

dispatch_apply 快速迭代方法。

通常我们会用for循环遍历,但是GCD给我们提供了快速迭代函数 dispatch_apply.

-(void)apply
{
 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"apply---begin");
    dispatch_apply(10, queue, ^(size_t index){
         NSLog(@"%zd -- %@",index,[NSThread currentThread]);
     });
    NSLog(@"apply--end");
 }

运行:

2020-11-13 15:19:52.111509+0800 GCD必备技能[76919:3685597] apply---begin
2020-11-13 15:19:52.111934+0800 GCD必备技能[76919:3685597] 1 -- <NSThread: 0x600002e84280>{number = 7, name = (null)}
2020-11-13 15:19:52.111929+0800 GCD必备技能[76919:3685587] 2 -- <NSThread: 0x600002e9c340>{number = 8, name = (null)}
2020-11-13 15:19:52.111986+0800 GCD必备技能[76919:3685588] 3 -- <NSThread: 0x600002e84400>{number = 5, name = (null)}
2020-11-13 15:19:52.111989+0800 GCD必备技能[76919:3685593] 0 -- <NSThread: 0x600002e8cc00>{number = 6, name = (null)}
2020-11-13 15:19:52.112157+0800 GCD必备技能[76919:3685597] 4 -- <NSThread: 0x600002e84280>{number = 7, name = (null)}
2020-11-13 15:19:52.112210+0800 GCD必备技能[76919:3685587] 5 -- <NSThread: 0x600002e9c340>{number = 8, name = (null)}
2020-11-13 15:19:52.112647+0800 GCD必备技能[76919:3685588] 6 -- <NSThread: 0x600002e84400>{number = 5, name = (null)}
2020-11-13 15:19:52.112717+0800 GCD必备技能[76919:3685593] 7 -- <NSThread: 0x600002e8cc00>{number = 6, name = (null)}
2020-11-13 15:19:52.112735+0800 GCD必备技能[76919:3685597] 8 -- <NSThread: 0x600002e84280>{number = 7, name = (null)}
2020-11-13 15:19:52.112806+0800 GCD必备技能[76919:3685587] 9 -- <NSThread: 0x600002e9c340>{number = 8, name = (null)}
2020-11-13 15:19:52.115074+0800 GCD必备技能[76919:3685597] apply--end
  • dispatch_apply函数 按照指定次数将任务追加到指定的队列中。并等待全部队列执行结束。
  • 如果在串行队列,和for的使用是一样的。这也就提现不出快速迭代的意义。

GCD 队列组 􏲏􏲐􏴢􏱟dispatch_group

当我们有一个这样的需求。分别异步执行两个耗时任务。在两个耗时任务都完成之后,回到主线程 执行任务。 这时我们就可以用 队列组

-(void)groupNotify
{
    NSLog(@"currentThread --- %@",[NSThread currentThread]);
    NSLog(@"group---begin");
    dispatch_group_t grtoup = dispatch_group_create();
    dispatch_group_async(grtoup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        ///追加任务1
        for (int i = 0; i<2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
         }
     });
    
    dispatch_group_async(grtoup,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
         ///追加任务2
        for (int i = 0; i<2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2--%@",[NSThread currentThread]);
         }
     });
    
    dispatch_group_notify(grtoup, dispatch_get_main_queue(), ^{
       ///等前面的 异步任务1、任务 2都执行完毕后,回到主线程 执行下边任务
         NSLog(@"group ---end");
        
    });
 }

运行

2020-11-13 15:44:49.110868+0800 GCD必备技能[77112:3697594] currentThread --- <NSThread: 0x60000114ca00>{number = 1, name = main}
2020-11-13 15:44:49.111107+0800 GCD必备技能[77112:3697594] group---begin
2020-11-13 15:44:51.116700+0800 GCD必备技能[77112:3697686] 2--<NSThread: 0x600001177b00>{number = 3, name = (null)}
2020-11-13 15:44:51.116701+0800 GCD必备技能[77112:3697679] 1---<NSThread: 0x600001142000>{number = 6, name = (null)}
2020-11-13 15:44:53.121983+0800 GCD必备技能[77112:3697679] 1---<NSThread: 0x600001142000>{number = 6, name = (null)}
2020-11-13 15:44:53.122046+0800 GCD必备技能[77112:3697686] 2--<NSThread: 0x600001177b00>{number = 3, name = (null)}
2020-11-13 15:44:53.122441+0800 GCD必备技能[77112:3697594] group ---end
  • 从上面dome可以看出 在1 2 任务 全部完成之后回到 主线程 end. 1 2 任务没有先后顺序 和任务的复杂度有关。

dispatch_group_wait

暂停当前线程(阻塞当前线程),等待指定的group中的任务执行完成之后,才会往下走。

-(void)groupWait
{
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"group--begin");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         // 􏳂􏲭􏱇􏱈1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2]; NSLog(@"1---%@",[NSThread currentThread]);}
     });
 
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2]; NSLog(@"2---%@",[NSThread currentThread]);}
        
    });
    //等待 上面 的任务全部完成后,会往下继续执行(会阻塞当前线程);
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group---end");
 }

运行

2020-11-13 15:52:12.205902+0800 GCD必备技能[77212:3701681] currentThread---<NSThread: 0x600001de4980>{number = 1, name = main}
2020-11-13 15:52:12.206076+0800 GCD必备技能[77212:3701681] group--begin
2020-11-13 15:52:14.208172+0800 GCD必备技能[77212:3701777] 1---<NSThread: 0x600001da16c0>{number = 2, name = (null)}
2020-11-13 15:52:14.208172+0800 GCD必备技能[77212:3701776] 2---<NSThread: 0x600001da8900>{number = 5, name = (null)}
2020-11-13 15:52:16.213471+0800 GCD必备技能[77212:3701776] 2---<NSThread: 0x600001da8900>{number = 5, name = (null)}
2020-11-13 15:52:16.213547+0800 GCD必备技能[77212:3701777] 1---<NSThread: 0x600001da16c0>{number = 2, name = (null)}
2020-11-13 15:52:16.214046+0800 GCD必备技能[77212:3701681] group---end
  • 从上面运行结果 可以看出: 当所有的任务执行完成之后,才执行 dispatch_group_wait 之后的操作。但是,使用 dispatch_group_wait 会阻塞当前线程。

dispatch_group_enter 、􏱪dispatch_group_leave

dispatch_group_enter 标志着 一个任务追加到了 group, 执行一次,相当于 group中未执行完毕任务+1

dispatch_group_leave 标志着 一个任务 离开了group,执行一次,相当于 group中未执行完毕 任务 数 -1.

当 group中未执行完毕任务数为 0 的时候,才会使 dispatch_group_wait解除阻塞,以及执行追加到 dispatch_group_notify中的任务。

- (void)groupEnterAndLeave
{
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"group--begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
       ///追加任务q1
        for (int i = 0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
         }
        dispatch_group_leave(group);
     });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i = 0; i<2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2--%@",[NSThread currentThread]);
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        ///等前面的异步操作都执行完毕后,回到主线程
        for (int i = 0; i<2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2--%@",[NSThread currentThread]);
        }
     });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group--end");
 
}

运行

2020-11-13 16:01:46.291495+0800 GCD必备技能[77428:3708794] currentThread---<NSThread: 0x6000006b8400>{number = 1, name = main}
2020-11-13 16:01:46.291716+0800 GCD必备技能[77428:3708794] group--begin
2020-11-13 16:01:48.293155+0800 GCD必备技能[77428:3708960] 2--<NSThread: 0x6000006fe9c0>{number = 4, name = (null)}
2020-11-13 16:01:48.293154+0800 GCD必备技能[77428:3708959] 1---<NSThread: 0x6000006e8380>{number = 7, name = (null)}
2020-11-13 16:01:50.294844+0800 GCD必备技能[77428:3708959] 1---<NSThread: 0x6000006e8380>{number = 7, name = (null)}
2020-11-13 16:01:50.294840+0800 GCD必备技能[77428:3708960] 2--<NSThread: 0x6000006fe9c0>{number = 4, name = (null)}
2020-11-13 16:01:50.295342+0800 GCD必备技能[77428:3708794] group--end
2020-11-13 16:01:52.311488+0800 GCD必备技能[77428:3708794] 2--<NSThread: 0x6000006b8400>{number = 1, name = main}
2020-11-13 16:01:54.311982+0800 GCD必备技能[77428:3708794] 2--<NSThread: 0x6000006b8400>{number = 1, name = main}
  • 从这里可以看出 当所有任务执行完毕之后 才执行的dispatch_group_notify 中的任务,当 group中未完毕任务数为0 的时候,才会解除 dispatch_group_wait 真的阻塞
  • dispatch_group_enter、dispatch_group_leave组合,其实等同于 dispatch_group_async

GCD信号量: dispatch_semaphore

GCD中的信号量是指的Dispatch Semaphore ,是持有技术的信号。类似过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在Dispatch Semaphore中,使用计数来完成这个功能,计数为0(即减1前信号量值为0)时等待,不可通过,计数为1或者大于1时,技术减1且不等待,可通过。Dispatch Semaphore 提供了三个函数

  • dispatch_semaphore_create: 创建信号量总值
  • dispatch_semaphore_signal : 信号量加1
  • dispatch_semaphore_wait: 信号量减1
-(void)semaphoreSync
{
    NSLog(@"currentThread--%@",[NSThread currentThread]);//打印当前线程
    NSLog(@"semaphore-- begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    ///创建信号量为0
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    ///开启异步线程
    dispatch_async(queue, ^{
        
        NSLog(@"线程asyn1%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2];
        dispatch_semaphore_signal(semaphore);
        
    });
   /// 如 wait函数 -1 后 信号量小于 0 会阻塞当前线程并进入等待
   /// 走到这里为 信号量 为0 通过下面函数 进行减1  所以小于0 当前线程被阻塞等待状态。
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
 
   /// 当前面异步函数 发送加1 信号的时候此时dispatch_semaphore_wait 解除阻塞来到这里
    NSLog(@"最终执行   ---%@",[NSThread currentThread]);
}

运行

2020-11-16 10:32:33.726876+0800 GCD必备技能[79501:3832306] currentThread--<NSThread: 0x600001454980>{number = 1, name = main}
2020-11-16 10:32:33.727332+0800 GCD必备技能[79501:3832306] semaphore-- begin
2020-11-16 10:32:33.727915+0800 GCD必备技能[79501:3832384] 线程asyn1<NSThread: 0x6000014635c0>{number = 4, name = (null)}
2020-11-16 10:32:35.733333+0800 GCD必备技能[79501:3832306] 最终执行   ---<NSThread: 0x600001454980>{number = 1, name = main}

  • 从上面dome中我们就能 看出 在写 dispatch_semaphore_wait 之前的信号量为0 的时候 会阻塞 当前线程。当异步函数里的任务dispatch_semaphore_signal 发送信号时候。当前线程 才解开阻塞执行下面任务。

GCD信号量 异步并发队列任务 同步执行

从上面的例子我们已经能理解 dispatch_semaphore_wait 在什么时候阻塞,什么时候取消阻塞 下面是多个异步任务的时候 如何变同步。

- (void)asyncConcurrent
{
    ///创建信号总量为0
    dispatch_semaphore_t  sem = dispatch_semaphore_create(0);
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"asyncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{ // 1
        
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
         }
        ///发送信号
        dispatch_semaphore_signal(sem);
    });
    
    ///等待前面任务完成信号
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    
    dispatch_async(queue, ^{ // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
        //发送信号
        dispatch_semaphore_signal(sem);
    });
    
    ///等待前面任务完成信号
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    
    dispatch_async(queue, ^{ // 3
        for (int i = 0; i < 2; ++i)
        {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
        //发送信号
        dispatch_semaphore_signal(sem);
    });
    ///等待前面任务完成信号
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

    NSLog(@"asyncConcurrent---end");
}

运行:

2020-11-16 11:11:29.259636+0800 GCD必备技能[79803:3851113] currentThread---<NSThread: 0x6000026400c0>{number = 1, name = main}
2020-11-16 11:11:29.259834+0800 GCD必备技能[79803:3851113] asyncConcurrent---begin
2020-11-16 11:11:31.260350+0800 GCD必备技能[79803:3851185] 1---<NSThread: 0x60000264d580>{number = 4, name = (null)}
2020-11-16 11:11:33.262047+0800 GCD必备技能[79803:3851185] 1---<NSThread: 0x60000264d580>{number = 4, name = (null)}
2020-11-16 11:11:35.267828+0800 GCD必备技能[79803:3851185] 2---<NSThread: 0x60000264d580>{number = 4, name = (null)}
2020-11-16 11:11:37.268716+0800 GCD必备技能[79803:3851185] 2---<NSThread: 0x60000264d580>{number = 4, name = (null)}
2020-11-16 11:11:39.269517+0800 GCD必备技能[79803:3851185] 3---<NSThread: 0x60000264d580>{number = 4, name = (null)}
2020-11-16 11:11:41.272011+0800 GCD必备技能[79803:3851185] 3---<NSThread: 0x60000264d580>{number = 4, name = (null)}
2020-11-16 11:11:41.272481+0800 GCD必备技能[79803:3851113] asyncConcurrent---end

GCD信号量 异步函数队列组 同步操作

-(void)groupNotify
{
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    NSLog(@"currentThread --- %@",[NSThread currentThread]);
    NSLog(@"group---begin");
    
    
    dispatch_group_t grtoup = dispatch_group_create();
    dispatch_group_async(grtoup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        ///追加任务1
        for (int i = 0; i<2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
         }
    ///发送信号
        dispatch_semaphore_signal(sem);
    });
    ///   等待任务1执行完 信号
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    
    dispatch_group_async(grtoup,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
         ///追加任务2
        for (int i = 0; i<2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2--%@",[NSThread currentThread]);
         }
     
     });
 
    
    dispatch_group_notify(grtoup, dispatch_get_main_queue(), ^{
       ///等前面的 异步任务1、任务 2都执行完毕后,回到主线程 执行下边任务
         NSLog(@"group ---end");
        
    });
 }

运行

2020-11-16 11:21:24.174965+0800 GCD必备技能[79923:3857362] currentThread --- <NSThread: 0x600002d14200>{number = 1, name = main}
2020-11-16 11:21:24.175152+0800 GCD必备技能[79923:3857362] group---begin
2020-11-16 11:21:26.178220+0800 GCD必备技能[79923:3857514] 1---<NSThread: 0x600002d3a480>{number = 2, name = (null)}
2020-11-16 11:21:28.180172+0800 GCD必备技能[79923:3857514] 1---<NSThread: 0x600002d3a480>{number = 2, name = (null)}
2020-11-16 11:21:30.185969+0800 GCD必备技能[79923:3857514] 2--<NSThread: 0x600002d3a480>{number = 2, name = (null)}
2020-11-16 11:21:32.187944+0800 GCD必备技能[79923:3857514] 2--<NSThread: 0x600002d3a480>{number = 2, name = (null)}
2020-11-16 11:21:32.188372+0800 GCD必备技能[79923:3857362] group ---end

  • 分析上面例子 ,在当前任务创建了个任务总量为0 的信号量对象。此时的并发队列有两组异步函数执行任务。如不考虑到信号量,此时任务1 任务2 没有顺序。等1 2 任务都完成后执行 通知回到主线程。此时我们有个需求 当任务1 完成之后 才会执行 任务2 最后回到主线程通知。首先在 两个异步函数(dispatch_group_async)中间执行等待信号。并在1任务完成之后发送信号。解除阻塞 执行 2任务。最后在通知 全部完成的任务。这样也就达到了我们的目的。
  • 为啥会等待? 因为此时在写dispatch_semaphore_wait 的前面 信号总量 为0 。

GCD信号量 保证线程安全

对于 信号量 上面两个例子 我们已经能很好的能购运用,那么 信号量 可以保证线程安全吗?答案显而易见。如何防止多线程的争夺? 下面来举例子,就拿多窗口进行卖票 在演示

-(void)initTicketStatusSave
{
    NSLog(@"currentThread--%@",[NSThread currentThread]);//打印当前线程
    NSLog(@"semaphore--begin");
    ///创建信号量  为1
    _semaphoreLock = dispatch_semaphore_create(2);
    self.ticketSurplusCount = 50;
    //queue1 代表北京火车售卖窗口
    dispatch_queue_t queue1 =  dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    //queue2 代表上海火车售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
            [weakSelf saleTicketSafe];
    
        });
    dispatch_async(queue2, ^{
       [weakSelf saleTicketSafe];
     });
 }
 
-(void)saleTicketSafe{
    while (1) {
        ///相当于加锁  引用计数 -1
        dispatch_semaphore_wait(_semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount>0) {
            self.ticketSurplusCount --;
            NSLog(@"%@",[NSString stringWithFormat:@"剩余票数:%ld 窗口:%@",(long)self.ticketSurplusCount,[NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }else{
            NSLog(@"所有火车票均已售完");
            dispatch_semaphore_signal(_semaphoreLock);
            break;
         }
         //相当于解锁  引用计数+1
        dispatch_semaphore_signal(_semaphoreLock);
     }
 }

Dispatch_source 源

  • CPU负荷非常小,尽量不占资源

  • 联结的优势

  • 任何线程调用它的函数dispatch_source_merge_data后,会执行DispatchSource事先定义好的句柄(可以把句柄简单理解为一个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: 挂起
dispatch_cancel: 取消
 

dome 熟悉用法

-(void)Dispatch_source{
    
    __block NSInteger totalComplete = 0;
    
    // 创建串行队列
    dispatch_queue_t queue =  dispatch_queue_create("com.hh.test", NULL);
    
    // 创建主队列源,源类型为 DISPATCH_SOURCE_TYPE_DATA_ADD
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
    // 设置源事件回调
    dispatch_source_set_event_handler(source, ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
        NSUInteger value = dispatch_source_get_data(source);
        
        totalComplete += value;
        
        NSLog(@"进度: %.2f", totalComplete/100.0);
        
    });
    
    // 开启源事件
    dispatch_resume(source);
    
    // 发送数据源
    for (int i= 0; i<100; i++) {
        
        dispatch_async(queue, ^{
            
            sleep(1);
            
            // 发送源数据
            dispatch_source_merge_data(source, 1);
        });
    }
    
    
}

运行

2020-11-16 16:51:20.161331+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
2020-11-16 16:51:20.161564+0800 GCD必备技能[82155:4005329] 进度: 0.01
2020-11-16 16:51:21.163870+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
2020-11-16 16:51:21.164087+0800 GCD必备技能[82155:4005329] 进度: 0.02
2020-11-16 16:51:22.166983+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
2020-11-16 16:51:22.167195+0800 GCD必备技能[82155:4005329] 进度: 0.03
2020-11-16 16:51:23.170617+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
2020-11-16 16:51:23.170802+0800 GCD必备技能[82155:4005329] 进度: 0.04
2020-11-16 16:51:24.173305+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
2020-11-16 16:51:24.173590+0800 GCD必备技能[82155:4005329] 进度: 0.05
2020-11-16 16:51:25.178286+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
2020-11-16 16:51:25.178471+0800 GCD必备技能[82155:4005329] 进度: 0.06
2020-11-16 16:51:26.182452+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
2020-11-16 16:51:26.182658+0800 GCD必备技能[82155:4005329] 进度: 0.07
2020-11-16 16:51:27.186273+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
2020-11-16 16:51:27.186547+0800 GCD必备技能[82155:4005329] 进度: 0.08
2020-11-16 16:51:28.187115+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
2020-11-16 16:51:28.187361+0800 GCD必备技能[82155:4005329] 进度: 0.09
2020-11-16 16:51:29.190399+0800 GCD必备技能[82155:4005329] <NSThread: 0x6000024c8200>{number = 1, name = main}
... 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342