【iOS】GCD深入学习

这篇具有很好参考价值的文章主要介绍了【iOS】GCD深入学习。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

栅栏方法:dispatch_barrier_async

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

【iOS】GCD深入学习,ios,学习,cocoa

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

- (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深入学习,ios,学习,cocoa

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

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深入学习,ios,学习,cocoa

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

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

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

- (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深入学习,ios,学习,cocoa

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

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

- (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深入学习,ios,学习,cocoa

实际的运行结果是栅栏前的任务组(也就是任务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深入学习,ios,学习,cocoa

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

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

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

- (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深入学习,ios,学习,cocoa

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

- (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深入学习,ios,学习,cocoa

这种情况下的栅栏和任务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深入学习,ios,学习,cocoa

实际运行过程中,任务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深入学习,ios,学习,cocoa

具体的运行情况是:先打印了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深入学习,ios,学习,cocoa

因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是 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深入学习,ios,学习,cocoa

由于添加到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);
}

结果:

【iOS】GCD深入学习,ios,学习,cocoa

可以看到打印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!!");
}

结果:
【iOS】GCD深入学习,ios,学习,cocoa

可以看到就是在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(@"全部完成");
    });
}

结果:
【iOS】GCD深入学习,ios,学习,cocoa

我们可以看到:任务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深入学习,ios,学习,cocoa

可以看到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;
        }
        
    }
}

结果:
【iOS】GCD深入学习,ios,学习,cocoa

可以看到在不考虑线程安全,不使用 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);
    }
}

结果:
【iOS】GCD深入学习,ios,学习,cocoa

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

可以看出,在考虑了线程安全的情况下,使用 dispatch_semaphore 机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。文章来源地址https://www.toymoban.com/news/detail-622817.html

到了这里,关于【iOS】GCD深入学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【mysql进阶-彩蛋篇】深入理解顺序io和随机io(全网最详细篇)

    MySql系列整体栏目 内容 链接地址 【一】深入理解mysql索引本质 https://blog.csdn.net/zhenghuishengq/article/details/121027025 【二】深入理解mysql索引优化以及explain https://blog.csdn.net/zhenghuishengq/article/details/124552080 【三】深入理解mysql的索引分类,覆盖索引(失效),回表,MRR https://bl

    2024年02月05日
    浏览(44)
  • 深入理解Linux网络——本机网络IO

    系列文章: 深入理解Linux网络——内核是如何接收到网络包的 深入理解Linux网络——内核与用户进程协作之同步阻塞方案(BIO) 深入理解Linux网络——内核与用户进程协作之多路复用方案(epoll) 深入理解Linux网络——内核是如何发送网络包的 深入理解Linux网络——本机网络

    2024年02月15日
    浏览(47)
  • JavaSE进阶 | 深入理解Java IO流(文件专属流)

    目录 🥅IO流理论概述 1.什么是IO 2.IO流的分类 3.流的四大家族 4.需要掌握的十六个流 🥅字节输入流FileInputStream 1.FileInputStream初步理解 2.FileInputStream常用方法 🥅字节输出流FileOutputStream 🥅任意文件拷贝 🥅FileReader FileWriter 普通文件拷贝 1.字符输入流FileReader 2.字符输出流FileW

    2023年04月12日
    浏览(39)
  • iOS脱壳技术(二):深入探讨dumpdecrypted工具的高级使用方法

    应用程序脱壳是指从iOS应用程序中提取其未加密的二进制可执行文件,通常是Mach-O格式。这可以帮助我们深入研究应用程序的底层代码、算法、逻辑以及数据结构。这在逆向工程、性能优化、安全性分析等方面都有着重要的应用。 在上一篇内容中我们已经介绍了Clutch的脱壳技

    2024年02月11日
    浏览(42)
  • 高性能网络设计秘笈:深入剖析Linux网络IO与epoll

    本文分享自华为云社区《高性能网络设计秘笈:深入剖析Linux网络IO与epoll》,作者: Lion Long 。 epoll是Linux内核中一种可扩展的IO事件处理机制,可替代select和poll的系统调用。处理百万级并发访问性能更佳。 (1) 文件描述符越多,性能越差。 单个进程中能够监视的文件描述

    2024年02月16日
    浏览(50)
  • 文件IO,目录IO的学习

    用法:#include“head.h”    -     在当前目录下寻找头文件 主函数的传参中,argc是传参的个数  ,const char *argv[]是一个指针数组,存放的指针类型数据 argv【n】,n=1/2/3 分别代表三个指针参数 标准示例:     off_t lseek(int fd, off_t offset, int whence);        功能:            

    2024年02月20日
    浏览(32)
  • Linux性能学习(3.2):IO_磁盘IO

    参考资料: 1. Linux I/O模型 2. 判断磁盘I/O是否饱和与%util指标的意义 3. 磁盘利用率和饱和度 4. 辩证看待 I/Ostat 在上一篇中,大致了解了文件系统的一些知识,了解了不同的文件系统以及VFS的概念,其实在存储介质上也是有这个情况,在嵌入式开发中,会根据不同的项目使用不

    2024年02月08日
    浏览(53)
  • ios客户端学习笔记(七):iOS客户端的UI设计

    iOS客户端的UI设计是指在iOS操作系统上开发应用程序时所涉及的用户界面设计,包括应用程序的布局、颜色、字体、图标等元素的设计。良好的UI设计应该能够提高用户体验,使用户能够轻松地使用应用程序。 在iOS客户端的UI设计中,需要考虑以下几个方面: 应用程序布局应

    2023年04月26日
    浏览(49)
  • 【iOS】ARC学习

    在学习ARC之前,先来复习一下内存管理以及autorelease的实现 先来看一下GNUstep源代码: autorelease 其本质就是调用 NSAutoreleasePool 对象的 addObject 类方法,就是 将对象加到自动释放池中 接下来再看一下废弃自动释放池的一些功能函数 可使用 showPools 输出现在的NSAutoreleasePool的状况

    2024年03月17日
    浏览(36)
  • IOS-闭包学习-Swift

    闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。 闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕

    2024年01月24日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包