【iOS】GCD深入学习

关于GCD和队列的简单介绍请看:【iOS】GCD学习

本篇主要介绍GCD中的方法。

栅栏方法:dispatch_barrier_async

我们有时候需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作,当然操作组里也可以包含一个或者多个任务
这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏
dispatch_barrier_async方法会等待前边追加到并发队列中的任务全部执行完毕后,再将制定的任务追加到该异步队列中。然后在dispatch_barrier_async方法追加的任务执行完毕之后,接着追加任务到该异步队列并开始执行,大佬博客中的示意图非常的形象,具体图示如下:

【iOS】GCD深入学习_第1张图片

栅栏方法的代码使用样例如下:

- (void) barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queue, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        dispatch_barrier_async(queue, ^{
            // 追加任务 barrier
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        });
        
        dispatch_async(queue, ^{
            // 追加任务 3
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queue, ^{
            // 追加任务 4
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        });

}

结果:
【iOS】GCD深入学习_第2张图片

在执行完栅栏前面的操作之后执行栅栏操作,最后再执行栅栏后面的操作。

GCD中同步栅栏和异步栅栏

我们之前考虑异步栅栏+单一队列的时候栅栏只作用于同一队列

那么对于身处不同队列的任务又有什么样的拦截作用呢?

对于重要的栅栏方法部分,我们将各种情况都实验一下:

异步栅栏+单一串行队列:

(由于异步执行+串行队列本身就是在创建的唯一一个新线程里按任务添加顺序排队执行,所以其实在这种情况下添加栅栏是没有意义的)

- (void) asyncBarrierAndOneSerial {
    dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_SERIAL);
        
        dispatch_async(queue, ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queue, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        dispatch_barrier_async(queue, ^{
            // 追加任务 barrier
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        });
        
        dispatch_async(queue, ^{
            // 追加任务 3
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queue, ^{
            // 追加任务 4
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        });
}

结果:
【iOS】GCD深入学习_第3张图片

异步栅栏+单一并行队列:

(该种情况上方已经讲述过了)

同步栅栏+单一串行队列:

- (void)syncBarrierAndOneSerial {
    dispatch_queue_t queue = dispatch_queue_create("net.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];            // 模拟耗时操作
        NSLog(@"1--%@", [NSThread currentThread]);    // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务2
        [NSThread sleepForTimeInterval:2];            // 模拟耗时操作
        NSLog(@"2--%@", [NSThread currentThread]);    // 打印当前线程
    });
    
    dispatch_barrier_sync(queue, ^{
        // 追加任务 barrier
        [NSThread sleepForTimeInterval:2];            // 模拟耗时操作
        NSLog(@"2--%@", [NSThread currentThread]);    // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务3
        [NSThread sleepForTimeInterval:2];            // 模拟耗时操作
        NSLog(@"3--%@", [NSThread currentThread]);    // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务4
        [NSThread sleepForTimeInterval:2];            // 模拟耗时操作
        NSLog(@"4--%@", [NSThread currentThread]);    // 打印当前线程
    });
}

结果:
【iOS】GCD深入学习_第4张图片

我们可以看到在串行队列中无论是同步执行还是异步执行,都是排好队一个一个按顺序来执行的。

同步栅栏+单一并行队列:

- (void)syncBarrierAndOneConcurrent {
    dispatch_queue_t queue =  dispatch_queue_create("net.testQuquq", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];             // 模拟耗时操作
        NSLog(@"1--%@", [NSThread currentThread]);     // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务2
        [NSThread sleepForTimeInterval:2];             // 模拟耗时操作
        NSLog(@"2--%@", [NSThread currentThread]);     // 打印当前线程
    });
    
    dispatch_barrier_sync(queue, ^{
        // 追加barrier
        [NSThread sleepForTimeInterval:2];             // 模拟耗时操作
        NSLog(@"barrier--%@", [NSThread currentThread]);     // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务3
        [NSThread sleepForTimeInterval:2];             // 模拟耗时操作
        NSLog(@"3--%@", [NSThread currentThread]);     // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务4
        [NSThread sleepForTimeInterval:2];             // 模拟耗时操作
        NSLog(@"4--%@", [NSThread currentThread]);     // 打印当前线程
    });
}

运行结果:

【iOS】GCD深入学习_第5张图片

实际的运行结果是栅栏前的任务组(也就是任务1和任务2),在程序开始执行两秒之后同时打印了结果,接着两秒的时间单独执行了栅栏中的方法,最后两秒时间同时执行了栅栏后的任务组(也就是任务3和任务4),而且由于栅栏前后的任务组中的任务都是在并行队列中异步执行,所以执行结束的顺序是不确定的。

异步栅栏+多个串行队列:

- (void)asyncBarrierAndSerials {
    dispatch_queue_t queue1 = dispatch_queue_create("net.testQueue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("net.testQueue2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("net.testQueue3", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue4 = dispatch_queue_create("net.testQueue4", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue5 = dispatch_queue_create("net.testQueue5", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue1, ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];           // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]);  // 打印当前线程
    });

    dispatch_async(queue2, ^{
        // 追加任务2
        [NSThread sleepForTimeInterval:2];           // 模拟耗时操作
        NSLog(@"2---%@", [NSThread currentThread]);  // 打印当前线程
    });

    dispatch_barrier_async(queue3, ^{
        // 追加任务 barrier
        [NSThread sleepForTimeInterval:2];           // 模拟耗时操作
        NSLog(@"barrier---%@", [NSThread currentThread]);  // 打印当前线程
    });
    
    dispatch_async(queue4, ^{
        // 追加任务4
        [NSThread sleepForTimeInterval:2];           // 模拟耗时操作
        NSLog(@"4---%@", [NSThread currentThread]);  // 打印当前线程
    });

    dispatch_async(queue5, ^{
        // 追加任务5
        [NSThread sleepForTimeInterval:2];           // 模拟耗时操作
        NSLog(@"5---%@", [NSThread currentThread]);  // 打印当前线程
    });
}

结果:
【iOS】GCD深入学习_第6张图片

异步栅栏+多个串行队列的情况下每个任务都是几乎同时执行的,五个任务执行的结束时间都是完全随机的,此时的栅栏也就失去了该有的意义。

异步栅栏+多个并行队列:

异步栅栏+多个串行队列情况的各任务执行结束时间都是完全随机的,所以异步栅栏+多个并行队列更是可想而知,肯定也是完全随机的。

- (void) asyncBarrierAndConcurrents {
    dispatch_queue_t queueFirst = dispatch_queue_create("net.testQueueFirst", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queueSecond = dispatch_queue_create("net.testQueueSecond", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queueThird = dispatch_queue_create("net.testQueueThird", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queueFourth = dispatch_queue_create("net.testQueueFourth", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queueFifth = dispatch_queue_create("net.testQueueFifth", DISPATCH_QUEUE_CONCURRENT);
    
    
        dispatch_async(queueFirst, ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queueSecond, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        dispatch_barrier_async(queueThird, ^{
            // 追加任务 barrier
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        });
        
        dispatch_async(queueFourth, ^{
            // 追加任务 3
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queueFifth, ^{
            // 追加任务 4
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        });
}

结果:
【iOS】GCD深入学习_第7张图片

同步栅栏+多个串行队列:

- (void) syncBarrierAndSerials {
    dispatch_queue_t queueFirst = dispatch_queue_create("net.testQueueFirst", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queueSecond = dispatch_queue_create("net.testQueueSecond", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queueThird = dispatch_queue_create("net.testQueueThird", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queueFourth = dispatch_queue_create("net.testQueueFourth", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queueFifth = dispatch_queue_create("net.testQueueFifth", DISPATCH_QUEUE_SERIAL);
    
    
        dispatch_async(queueFirst, ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queueSecond, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        dispatch_barrier_sync(queueThird, ^{
            // 追加任务 barrier
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        });
        
        dispatch_async(queueFourth, ^{
            // 追加任务 3
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queueFifth, ^{
            // 追加任务 4
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        });
}

结果:
【iOS】GCD深入学习_第8张图片

这种情况下的栅栏和任务1还有任务2是几乎同时执行同时先出结果的(而且每次栅栏都是第一个出结果),但是由于同步的栅栏占用了主线程,就导致栅栏后的任务3和任务4只能等到栅栏中的任务执行完成之后再开始去执行。

同步栅栏+多个并行队列:

- (void) syncBarrierAndConcurrents {
    dispatch_queue_t queueFirst = dispatch_queue_create("net.testQueueFirst", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queueSecond = dispatch_queue_create("net.testQueueSecond", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queueThird = dispatch_queue_create("net.testQueueThird", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queueFourth = dispatch_queue_create("net.testQueueFourth", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queueFifth = dispatch_queue_create("net.testQueueFifth", DISPATCH_QUEUE_CONCURRENT);
    
    
        dispatch_async(queueFirst, ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queueSecond, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        dispatch_barrier_sync(queueThird, ^{
            // 追加任务 barrier
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        });
        
        dispatch_async(queueFourth, ^{
            // 追加任务 3
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queueFifth, ^{
            // 追加任务 4
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        });
}

结果:
【iOS】GCD深入学习_第9张图片

实际运行过程中,任务1、任务2和栅栏都是同时先开始去执行的,而且三者执行结束的时间是不确定的,然而由于栅栏占用了主线程的原因,任务3和任务4只有等到栅栏执行完成之后才开始执行。

延时执行方法:dispatch_after

我们经常会遇到这样的需求:在指定时间(例如 3 秒)之后执行某个任务。这种情况就可以用 GCDdispatch_after 方法来实现。

需要注意的是:dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的。

- (void)after {
    NSLog(@"currentThread---%@", [NSThread currentThread]);   // 打印当前线程
    NSLog(@"asyncMain---begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // NSEC_PER_SEC是一个宏定义,通常用于表示一秒钟所包含的纳秒数。
        // 2.0 秒后异步追加任务代码到主队列,并开始执行
        NSLog(@"after---%@", [NSThread currentThread]);     // 打印当前线程
        NSLog(@"asyncMain---willEnd");
    });
}

结果:
【iOS】GCD深入学习_第10张图片

具体的运行情况是:先打印了asyncMain---begin,接着过了两秒后紧接着按顺序打印了after---<_NSMainThread: 0x60000110c900>{number = 1, name = main}asyncMain---willEnd

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

我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。

/**
 * 一次性代码(只执行一次)dispatch_once
 */
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行 1 次的代码(这里面默认是线程安全的)
    });
}

GCD 快速迭代方法:dispatch_apply

通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法dispatch_applydispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。

如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。

我们可以利用并发队列进行异步执行。比如说遍历 0~56 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以在多个线程中同时(异步)遍历多个数字。

还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法

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

运行结果如下:
【iOS】GCD深入学习_第11张图片

因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是 apply—end 一定在最后执行。这是因为 dispatch_apply 方法会等待全部任务执行完毕。

GCD 队列组:dispatch_group

有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。

调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enterdispatch_group_leave 组合来实现 dispatch_group_async
调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。

dispatch_group_notify

监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务:

- (void)group {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk0");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    //dispatch_group_notify会等到group中的处理全部结束时再开始执行
     //在group中的处理全部结束时,将第三个参数(block)追加到第二个参数所对应的queue中
     dispatch_group_notify(group, dispatch_get_main_queue(), ^{
         NSLog(@"done");
     });
}

运行结果:
【iOS】GCD深入学习_第12张图片

由于添加到group中的队列在多线程并发时的执行结果时间是不确定的,所以打印的顺序都是随机的(理论如此,不过任务的执行顺序可能受到提交的先后顺序的影响,尤其是当多个任务都被提交到同一个队列时。)。

dispatch_group_wait

另外我们也可以使用dispatch_group_wait(group, DISPATCH_TIME_FOREVER);第二个参数为dispatch_time_t类型,可以自定义来等待group中的处理全部结束

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

如果我们不去添加dispatch_group_wait来进行等待,那么由于group中的处理本身也是异步的,所以就会在group中的处理还没有执行完时就去执行其他的任务,例子如下:

- (void)groupWait {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk0");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    NSLog(@"YES!!");
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}

结果:

在这里插入图片描述

可以看到打印YES!!操作在group中的处理还没有执行完时就已经执行了。

而像下面这样:

- (void)groupWait {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk0");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"YES!!");
}

结果:
在这里插入图片描述

可以看到就是在group中全部的处理执行完之后再执行的打印YES!!操作

dispatch_group_wait 相关代码运行输出结果可以看出: 当所有任务执行完成之后,才执行 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 中的任务
接着我们来看一下通过 dispatch_group_enterdispatch_group_leave 配和来实现向group添加操作:

- (void)groupWithEnterAndLeave {
    // 首先 需要创建一个线程组
    dispatch_group_t group = dispatch_group_create();
    // 任务1
    dispatch_group_enter(group);
    void (^blockFirst)(int) = ^(int a){
        NSLog(@"任务%d完成!", a);
        dispatch_group_leave(group);
    };
    
    blockFirst(1);
    
    // 任务2
    dispatch_group_enter(group);
    void (^blockSecond)(int) = ^(int a){
        NSLog(@"任务%d完成!", a);
        dispatch_group_leave(group);
    };
    
    blockSecond(2);
    
    // 全部完成
    dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
        NSLog(@"全部完成");
    });
}

结果:
在这里插入图片描述

我们可以看到:任务1和任务2执行完成之后,才会执行全部完成中的任务。
dispatch_group_enterdispatch_group_leave 相关代码运行结果中可以看出:当所有任务执行完成之后,才执行 dispatch_group_notify 中的任务。这里的dispatch_group_enter、dispatch_group_leave 组合,其实等同于dispatch_group_async

不过使用dispatch_group_enterdispatch_group_leave 需要成对出现。

如果 dispatch_group_leave 的调用次数多于 dispatch_group_enter 的调用次数,程序会 crash

GCD 信号量:dispatch_semaphore

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

  • dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量。
  • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
  • dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。

注意: 信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。

Dispatch Semaphore 在实际开发中主要用于:

  • 保持线程同步,将异步执行任务转换为同步执行任务。
  • 保证线程安全,为线程加锁。

Dispatch Semaphore 线程同步
我们在开发中,会遇到这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。换句话说,相当于,将将异步执行任务转换为同步执行任务。

下面我们就来利用Dispatch Semaphore实现线程同步,将一步执行任务转换为同步执行任务:

- (void)semaphoreSync {
    NSLog(@"currentThread---%@", [NSThread currentThread]);      // 打印当前线程
    NSLog(@"semaphore---begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];      // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]);     // 打印当前线程
        
        number = 100;
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %d", number);
}

结果:
【iOS】GCD深入学习_第13张图片

可以看到semaphore---end 是在执行完 number = 100; 之后才打印的。而且输出结果 number 为 100。整个的执行顺序如下:

semaphore 初始创建时计数为 0
异步执行 将 任务 1 追加到队列之后,不做等待,接着执行 dispatch_semaphore_wait 方法,semaphore 减 1,此时 semaphore == -1,当前线程进入等待状态(后面的内容不执行,只执行我们所添加的任务1,等到dispatch_semaphore_signal操作使信号量计数``>=0时线程才会恢复正常运作)
然后,异步任务 1 开始执行。任务 1 执行到 dispatch_semaphore_signal 之后,总信号量加 1,此时 semaphore == 0,正在被阻塞的线程(主线程)恢复继续执行
最后打印 semaphore---end,number = 100
这样就实现了线程同步,将异步执行任务转换为同步执行任务。

Dispatch Semaphore 线程安全和线程同步(为线程加锁)

线程安全: 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步: 可理解为线程 A 和 线程 B 一块配合,线程 A 执行到一定程度时要依靠线程B 的某个结果,于是停下来,示意 线程B 运行;线程B 依言执行,再将结果给 线程A;线程A 再继续操作。

举个简单例子就是:两个人在一起聊天。两个人不能同时说话,避免听不清(操作冲突)。等一个人说完(一个线程结束操作),另一个再说(另一个线程再开始操作)。

下面,我们模拟火车票售卖的方式,实现 NSThread 线程安全和解决线程同步问题(例子借鉴自:大佬博客)。

场景: 总共有 50 张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。

非线程安全(不使用 semaphore)

先来看看不考虑线程安全的代码:

@interface ViewController ()

@property (nonatomic, assign) NSInteger ticketSurplusCount;

@end



/**
 * 非线程安全:不使用 semaphore
 * 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
 */
- (void)initTicketStatusNotSafe {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    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 saleTicketNotSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });
}

/**
 * 售卖火车票(非线程安全)
 */
- (void)saleTicketNotSafe {
    while (1) {
        
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            break;
        }
        
    }
}

结果:

可以看到在不考虑线程安全,不使用 semaphore 的情况下,得到票数是错乱的,而且同一张票可能会发生卖两遍的情况,这样显然不符合我们的需求,所以我们需要考虑线程安全问题。

线程安全(使用 semaphore 加锁)

考虑线程安全的代码:

@interface ViewController ()

@property (nonatomic, assign) NSInteger ticketSurplusCount;

@end


//创建一个全局信号量
dispatch_semaphore_t semaphoreLock;

/**
 * 线程安全:使用 semaphore 加锁
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */
- (void)initTicketStatusSafe {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    
    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) {
        // 相当于加锁
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}

结果:

思路: 此处我们采用了dispatch_semaphore 机制,买票的操作是每次异步执行的,但是如果第一张票还没卖出去第二张票已经开始卖了的话就会由于dispatch_semaphore_wait操作使得信号量计数=-1,线程就会进入等待状态,等待第一张票卖完之后的dispatch_semaphore_signal操作,这个操作会让信号量的计数=1,使得线程重写开始正常运行,开始正常执行卖第二张票的处理,以此类推,通过保护每一次的卖票从而实现整个售票流程的正确性。

可以看出,在考虑了线程安全的情况下,使用 dispatch_semaphore 机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。

你可能感兴趣的:(ios,学习,cocoa)