【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 秒)之后执行某个任务。这种情况就可以用 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)(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_apply
。dispatch_apply
按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply
,那么就和 for
循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5
这 6
个数字,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_enter
、dispatch_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_wai
t来进行等待,那么由于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_enter
和 dispatch_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_enter
、dispatch_group_leave
相关代码运行结果中可以看出:当所有任务执行完成之后,才执行 dispatch_group_notify
中的任务。这里的dispatch_group_enter、dispatch_group_leave
组合,其实等同于dispatch_group_async
。
不过使用dispatch_group_enter
和 dispatch_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
机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。
相关文章:

【iOS】GCD深入学习
关于GCD和队列的简单介绍请看:【iOS】GCD学习 本篇主要介绍GCD中的方法。 栅栏方法:dispatch_barrier_async 我们有时候需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作,当然操作组里也可以包含一个或者…...

Webpack开启本地服务器;HMR热模块替换;devServer配置;开发与生成环境的区分与配置
目录 1_开启本地服务器1.1_开启本地服务器原因1.2_webpack-dev-server 2_HMR热模块替换2.1_认识2.2_开启HMR2.3_框架的HMR 3_devServer配置3.1_host配置3.2_port、open、compress 4_开发与生成环境4.1_如何区分开发环境4.2_入口文件解析4.3_区分开发和生成环境配置 1_开启本地服…...

opencv 31-图像平滑处理-方框滤波cv2.boxFilter()
方框滤波(Box Filtering)是一种简单的图像平滑处理方法,它主要用于去除图像中的噪声和减少细节,同时保持图像的整体亮度分布。 方框滤波的原理很简单:对于图像中的每个像素,将其周围的一个固定大小的邻域内…...

Kubernetes关于cpu资源分配的设计
kubernetes资源 在K8s中定义Pod中运行容器有两个维度的限制: 资源需求(Requests):即运行Pod的节点必须满足运行Pod的最基本需求才能运行Pod。如 Pod运行至少需要2G内存,1核CPU。(软限制)资源限额(Limits):即运行Pod期间,可能内存使用量会增加,那最多能使用多少内存,这…...

Flink读取mysql数据库(java)
代码如下: package com.weilanaoli.ruge.vlink.flink;import com.ververica.cdc.connectors.mysql.source.MySqlSource; import com.ververica.cdc.connectors.mysql.table.StartupOptions; import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema; import org…...

小程序学习(五):WXSS模板语法
1.什么是WXSS WXSS是一套样式语言,用于美化WXML的组件样式,类似于网页开发中的CSS 2.WXSS和CSS的关系 WXSS模板样式-rpx 3.什么是rpx尺寸单位 4.rpx的实现原理 5.rpx与px之间的单位换算* WXSS模板样式-样式导入 6.什么是样式导入 使用WXSS提供的import语法,可以导入外联的样式…...

注解 @JsonFormat 与 @DateTimeFormat 的使用
文章目录 JsonFormat (双端互传)DateTimeFormat (前端传后端日期格式转化)情况一 前端是时间组件 <el-date-picker 或其他情况二 前端未设置组件 JsonFormat (双端互传) com.fasterxml.jackson.annotation.JsonFormat; 将字符串的时间转换成Date类型…...

Python实现决策树算法:完整源码逐行解析
决策树是一种常用的机器学习算法,它可以用来解决分类和回归问题。决策树的优点是易于理解和解释,可以处理数值和类别数据,可以处理缺失值和异常值,可以进行特征选择和剪枝等操作。决策树的缺点是容易过拟合,对噪声和不…...

Linux文本三剑客---grep、sed、awk
目录标题 1、grep1.1 命令格式1.2命令功能1.3命令参数1.4grep实战演练 2、sed2.1 认识sed2.2命令格式2.3常用选项options2.4地址定界2.5 编辑命令command2.6用法演示2.6.1常用选项options演示2.6.2地址界定演示2.6.3编辑命令command演示 3、awk3.1认识awk3.2常用命令选项3.3awk…...

局域网VoIP网络电话测试
0. 环境 ubuntu18或者ubuntu22 - SIP服务器 win10 - SIP客户端1 ubuntu18 - SIP客户端2 1. SIP服务器搭建asterisk 1.0 环境 虚拟机ubuntu18 或者ubuntu22 1.1 直接安装 sudo apt-get install asterisk 1.2 配置用户信息 分为两个部分,第一部分是修改genera…...

el-table 去掉边框(修改颜色)
原始: 去掉表格的border属性,每一行下面还会有一条线,并且不能再拖拽表头 为了满足在隐藏表格边框的情况下还能拖动表头,修改相关css即可,如下代码 <style lang"less"> .table {//避免单元格之间出现白…...

redis与MongoDB的区别
1.Redis与MongoDB的概念 1.1 MongoDB MongoDB 是由C语言编写的,是一个基于分布式文件存储的开源数据库系统。 在高负载的情况下,添加更多的节点,可以保证服务器性能。 MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB …...

CSS设置高度
要设置 article.content 的恰当高度,您可以使用 CSS 来控制元素的外观。有几种方法可以设置元素的高度,具体取决于你的需求和布局。 以下是几种常见的方法: 1. 固定高度:你可以直接为 article.content 设置一个固定的高度值&…...

开源免费用|Apache Doris 2.0 推出跨集群数据复制功能
随着企业业务的发展,系统架构趋于复杂、数据规模不断增大,数据分布存储在不同的地域、数据中心或云平台上的现象越发普遍,如何保证数据的可靠性和在线服务的连续性成为人们关注的重点。在此基础上,跨集群复制(Cross-Cl…...

【docker】docker-compose服务编排
目录 一、服务编排概念二、docker compose2.1 定义2.2 使用步骤2.3 docker-compose安装2.4 docker-compose卸载 三、编排示例 一、服务编排概念 1.微服务架构的应用系统中一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启…...

EdgeBox_tx1_A200 PyTorch v1.9.0 环境部署
大家好,我是虎哥,今天远程帮助几个小伙伴在A200 控制器上安装PyTorch v1.9.0 torchvision v0.10.0,中间也是经历了很多波折,当然,大部分是网络问题和版本适配问题,所以完事后,将自己完整可用的过…...

【雕爷学编程】MicroPython动手做(33)——物联网之天气预报
天气(自然现象) 是指某一个地区距离地表较近的大气层在短时间内的具体状态。而天气现象则是指发生在大气中的各种自然现象,即某瞬时内大气中各种气象要素(如气温、气压、湿度、风、云、雾、雨、闪、雪、霜、雷、雹、霾等ÿ…...

分库分表之基于Shardingjdbc+docker+mysql主从架构实现读写分离 (三)
本篇主要说明: 1. 因为这个mysql版本是8.0,所以当其中一台mysql节点挂掉之后,主从同步,甚至双向数据同步都失效了,所以本篇主要记录下当其中的节点挂掉之后如何再次生效。另外推荐大家使用mysql5.7的版本,这…...

探秘企业DevOps一体化平台建设终极形态丨IDCF
笔者从事为企业提供研发效能改进解决方案相关工作十几年,为国内上百家企业提供过DevOps咨询及解决方案落地解决方案,涉及行业包括:金融、通信、制造、互联网、快销等多种行业。 DevOps的核心是研发效能改进,效能的提升离不开强大…...

百度智能创做AI平台
家人们好,在数字化时代,人工智能正引领着一场前所未有的创新浪潮。今天,我们将为大家介绍百度智能创做AI平台,这个为创意赋能、助力创作者的强大工具。无论你是创意工作者、内容创作者,还是想要释放内心创造力的个人&a…...

Python 开发工具 Pycharm —— 使用技巧Lv.1
Basic code completion Ctrl空格 is available in the search field when you search for text in the current file CtrlF, so there is no need to type the entire string 基本代码完成Ctrl 空格可在搜索领域当你搜索文本在当前文件Ctrl F,所以没有必要整个字符串类型 To m…...

zookeeper --- 高级篇
一、zookeeper 事件监听机制 1.1、watcher概念 zookeeper提供了数据的发布/订阅功能,多个订阅者可同时监听某一特定主题对象,当该主题对象的自身状态发生变化时(例如节点内容改变、节点下的子节点列表改变等),会实时、主动通知所有订阅者 …...

TypeScript【enum 枚举】
导语 在 TypeScript 中,新增了很多具有特性的一些数据类型处理方法,enum 【枚举】就是其中,很具有代表性的一种,所以本章节就来聊聊 在 TypeScript 中如何去运用 enum 【枚举】。 枚举的概念: 枚举(Enum&am…...

SpringBoot项目增加logback日志文件
一、简介 在开发和调试过程中,日志是一项非常重要的工具。它不仅可以帮助我们快速定位和解决问题,还可以记录和监控系统的运行状态。Spring Boot默认提供了一套简单易用且功能强大的日志框架logback,本文将介绍如何在Spring Boot项目中配置和…...

复习之selinux的管理
一、什么是selinux? SELinux,Security Enhanced Linux 的缩写,也就是安全强化的 Linux,是由美国国家安全局(NSA)联合其他安全机构(比如 SCC 公司)共同开发的,旨在增强传统 Linux 操…...

无涯教程-Lua - 文件I/O
I/O库用于在Lua中读取和处理文件。 Lua中有两种文件操作,即隐式(Implicit)和显式(Explicit)操作。 对于以下示例,无涯教程将使用例文件test.lua,如下所示。 -- sample test.lua -- sample2 test.lua 一个简单的文件打开操作使用以下语句。…...

java+ssm民宿酒店客房推荐预订系统_2k78b--论文
摘 要 互联网日益成熟,走进千家万户,改变多个行业传统的工作方式。民宿推荐管理以用户需求为基础,借由发展迅猛的互联网平台实现民宿推荐管理的信息化,简化旧时民宿推荐管理所需的纸质记录这一繁杂过程,从而大幅提高民…...

Docker实战-关于Docker镜像的相关操作(一)
导语 镜像,Docker中三大核心概念之一,并且在运行Docker容器之前需要本地存储对应的镜像。那么下面我们就来介绍一下在Docker中如何使用镜像。 如何获取镜像? 镜像作为容器运行的前提条件,在Docker Hub上提供了各种各样的开放的…...

Jenkins Gerrit Trigger实践
1.创建Gerrit Trigger 2.jenkins master节点生成gerrit用户的密钥 这里的用户名得写登录gerrit后个人信息中的 Username 3.gerrit 配置刚刚jenkins生成密钥的公钥 4.gerrit 用户加入群组 不加这个群组,下一步测试就会报错“User aeshare has no capability conn…...

Xcode protobuf2.5添加arm64编译器补丁生成静态库
项目需求,protobuf源码编成静态库使用 但是,github上的protobuf源码没有对应arm64的编译器定义,编译出来的静态库使用时报错。 下面的连接是arm64编译器代码补丁包,把编译器代码放到src/google/protobuf/stubs/atomicops_intern…...