【iOS】GCD深入学习

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

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

栅栏方法:dispatch_barrier_async

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

在这里插入图片描述

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

- (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]);      // 打印当前线程});}

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

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

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]);      // 打印当前线程});
}

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

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

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

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

- (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]);    // 打印当前线程});
}

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

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

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

- (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]);     // 打印当前线程});
}

运行结果:

在这里插入图片描述

实际的运行结果是栅栏前的任务组(也就是任务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]);  // 打印当前线程});
}

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

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

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

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

- (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]);      // 打印当前线程});
}

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

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

- (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]);      // 打印当前线程});
}

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

这种情况下的栅栏和任务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]);      // 打印当前线程});
}

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

实际运行过程中,任务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");});
}

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

具体的运行情况是:先打印了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");
}

运行结果如下:
在这里插入图片描述

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

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

由于添加到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();// 任务1dispatch_group_enter(group);void (^blockFirst)(int) = ^(int a){NSLog(@"任务%d完成!", a);dispatch_group_leave(group);};blockFirst(1);// 任务2dispatch_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);
}

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

可以看到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 机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/18672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTTP——一、了解Web及网络基础

HTTP 一、使用HTTP协议访问Web二、HTTP的诞生1、为知识共享而规划Web2、Web成长时代3、驻足不前的HTTP 三、网络基础TCP/IP1、TCP/IP协议族2、TCP/IP的分层管理3、TCP/IP 通信传输流 四、与HTTP关系密切的协议&#xff1a;IP、TCP和DNS1、负责传输的 IP 协议2、确保可靠性的TCP…

Gartner:2022年全球IaaS公有云服务市场增长30%,首次突破1000亿美元

根据Gartner的统计结果&#xff0c;2022年全球基础设施即服务&#xff08;IaaS&#xff09;市场从2021年的928亿美元增长到1203亿美元&#xff0c;同比增长29.7%。亚马逊在2022年继续排在IaaS市场的第一名&#xff0c;其次是微软、阿里巴巴、谷歌和华为。 最新消息&#xff0c;…

制砖机系统比例控制阀放大器

制砖机系统是一种生产砖块的机器设备系统。该系统由多个部分组成&#xff0c;包括压力系统、模具和振动系统、烘干和烧制系统等。压力系统是制砖机的主要组成部分之一&#xff0c;它通过压力将原料压缩成一定形状和尺寸的块状&#xff0c;然后经过烘干和烧制等步骤&#xff0c;…

解决单节点es索引yellow

现象 单节点的es&#xff0c;自动创建索引后&#xff0c;默认副本个数为1&#xff0c;索引状态为yellow 临时解决 修改副本个数为0 永久解决 方法1、修改elasticsearch.yml文件&#xff0c;添加配置并重启es number_of_replicas&#xff1a;副本分片数&#xff0c;默认…

【数据结构】——线性表的相关习题

目录 题型一&#xff08;顺序表的存储结构&#xff09;题型二&#xff08;链表的判空&#xff09;题型三&#xff08;单链表的建立&#xff09;题型四&#xff08;顺序表、单链表的插入删除&#xff09; 题型一&#xff08;顺序表的存储结构&#xff09; 1、线性表的顺序存储结…

解决Git下载失败太慢

解决Git下载失败太慢 Git 官网下载地址: https://git-scm.com/downloads Windows 下载地址: https://git-scm.com/download/win 用官网的地址下载, 需要从github上下载, 由于国内某些原因, 下载速度缓慢, 还经常失败. 国内用户, 可以通过镜像的方式, 提高下载速度. 阿里镜…

Linux - 进程地址空间

引入 在学习C语言的时候&#xff0c;内存包括栈区、堆区、静态区 这个布局是内存吗&#xff1f; 不是&#xff01;&#xff01; 这是进程地址空间&#xff01; 下面测试一下&#xff1a; 11540是bash进程 我们修改一下源程序&#xff0c;在观察下结果 发现父进程的g_value的值不…

gitee修改代码提交操作步骤说明

一&#xff0c;简介 本文主要介绍如何从gitee仓库下载文件&#xff0c;本地修改&#xff0c;本地提交&#xff0c;然后再push到远程服务器的操作步骤。供参考&#xff0c;欢迎一起讨论交流~ 二&#xff0c;操作步骤 总的操作步骤分为以下几步 1&#xff0c;远程服务器下载文…

TI的IWR6843跑3D People Tracking(3D人体检测追踪实验)demo的上手教程

1.硬件准备 1.IWR6843板子 2.两个USB转串口模块&#xff08;因为我的是自己做的板子&#xff0c;板子上没有集成USB转串口芯片&#xff09; 2.软件准备 1.最新版本的CCS&#xff0c;注意后缀没有THEIA https://www.ti.com/tool/CCSTUDIO?DCMPdsp_ccs_v4&HQSccs 2.最新…

Linux(三)---------网络路由命令(route路由命令)

一.route路由命令 1.什么是route路由&#xff1f; 计算机之间的数据传输必须经过网络&#xff0c;网络可以直接两台计算机&#xff0c;也可以通过一个一个的节点去连接。路由可以理解为互联网的中转站&#xff0c;网络中的数据包就是通过一个一个的路由器转发到目的地的。 路…

微信小程序 - 解析富文本插件版们

一、html2wxml 插件版 https://gitee.com/qwqoffice/html2wxml 申请使用注意事项 插件版本解析服务是由 QwqOffice 完成&#xff0c;存在不稳定因素&#xff0c;如对稳定性有很高的要求&#xff0c;请自行搭建解析服务&#xff0c;或在自家服务器上直接完成解析。对于有关插…

私人网盘搭建(利用阿里云oss搭建)

1、个人网盘场景说明 个人网盘架构 使用ECS安装Cloudreve提供网盘服务&#xff0c;OSS提供存储服务。当用户使用个人网盘时&#xff0c;访问部署Cloudreve ECS的公网IP地址即可完成文件上传、下载、删除、分享等服务。 什么是Cloudreve Cloudreve可帮助您即刻构建出兼备自用…

视频太大怎么压缩变小?视频压缩技巧快来学

我们都知道&#xff0c;视频分辨率越高&#xff0c;文件体积也就越大&#xff0c;为了更好的存储、传输和播放&#xff0c;我们需要适当压缩视频的大小&#xff0c;那么怎么才能轻松的将视频文件压缩变小呢&#xff1f;下面就给大家分享几个简单的方法&#xff0c;一起来看看吧…

利用尺度因子方法恢复GRACE水储量变化

1.背景 重力恢复与气候实验&#xff08;GRACE&#xff09;观测地球重力势的时间变化。在考虑了大气和海洋效应后&#xff0c;每月到年际尺度上剩余的信号主要与陆地水储存&#xff08;TWS&#xff09;的变化有关。水储存变化的估计受到测量误差和噪声的信号退化影响&#xff0…

11年编码经验程序员惨遭淘汰解雇,原因竟是不会使用AI工具

近日&#xff0c;Twitter 上一名技术人分享了一个事件&#xff0c;即拥有11年Java编码经验、会 100% 手写代码的程序员因拒绝使用辅助代码工具&#xff0c;只想写可控的代码&#xff0c;竟败给一位仅有4年经验、却善用编码工具的后辈&#xff0c;惨遭面试淘汰。 当「拒绝使用编…

PHP代码审计——实操!

ctfshow PHP特性 web93 八进制与小数点 <?php include("flag.php"); highlight_file(__FILE__); if(isset($_GET[num])){$num $_GET[num];if($num4476){die("no no no!");}if(preg_match("/[a-z]/i", $num)){die("no no no!")…

建网站一般使用Windows还是liunx好?

建网站一般使用Windows还是liunx好&#xff1f; 1&#xff1b;服务器配置比较低时&#xff0c;最好使用linux系统。 对于一个电脑新手&#xff0c;刚开始做网站时&#xff0c;都会选择入门级的服务器&#xff0c;我刚开始做网站时&#xff0c;就是这样的。我购买了一台入门级服…

CS5265 USB-C to HDMI 4k@60Hz单转方案

CS5265AN是一款高性能Type-C/DP1.4至HDMI2.0b转换器芯片&#xff0c;集成了DP1.4兼容接收机和HDMI2.0b兼容发射机&#xff0c;还配备了CC控制器用于CC通信&#xff0c;实现DP Alt模式。DP接口包括4条主通道、辅助通道和HPD信号&#xff0c;接收器支持每通道最大5.4Gbps数据速率…

浅入浅出MySQL事务

什么是事务 事务是由数据库中一系列的访问和更新组成的逻辑执行单元。 事务的逻辑单元中可以是一条SQL语句&#xff0c;也可以是一段SQL逻辑&#xff0c;这段逻辑要么全部执行成功&#xff0c;要么全部执行失败。 事务处理的基本原则是“原子性”、“一致性”、“隔离性”和…

114.(cesium篇)cesium去掉时间轴并用按钮控制运动

地图之家总目录(订阅之前必须详细了解该博客) 地图之家:cesium+leaflet+echart+地图数据+地图工具等相关内容的介绍 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: cesium去掉时间轴并用按钮控制运动 下面献上完整代码,代码重要位…