最近在梳理iOS线程知识点,把常用的写下来,一方面方便记忆,也有利于后面复习,关于GCD的线程队列的方法这里就不再细致讲述,除了阻塞的情况需要注意下,另一篇文章中有专门针对阻塞做了介绍,其他串行、并行、同步、异步组合使用都好理解;这里就介绍下根据项目业务情况,常会使用到的一些GCD的方法
一:dispatch_after
/**
* 延时执行方法 dispatch_after
*/
- (void)dispatchAfter {
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(), ^{
// 2.0 秒后异步任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]);
});
}
2020-01-14 19:56:50.174397+0800 Practice[30060:2516484] currentThread---<NSThread: 0x60000106ad80>{number = 1, name = main}
2020-01-14 19:56:50.174535+0800 Practice[30060:2516484] asyncMain---begin
2020-01-14 19:56:52.174763+0800 Practice[30060:2516484] after---<NSThread: 0x60000106ad80>{number = 1, name = main}
这里需要说明的是,dispatch_after不是在2秒之后开始执行任务,而是在2秒之后才开始把任务添加到队列中 。
二、dispatch_apply
/**
* 快速迭代方法 dispatch_apply
*/
- (void)dispatchApply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
NSTimeInterval start1 = [[NSDate date] timeIntervalSince1970];
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
输出日志:
2020-01-14 20:01:19.805576+0800 Practice[30117:2521131] apply---begin
2020-01-14 20:01:19.805779+0800 Practice[30117:2521131] 0---<NSThread: 0x600002f31280>{number = 1, name = main}
2020-01-14 20:01:19.805848+0800 Practice[30117:2521183] 1---<NSThread: 0x600002f13a40>{number = 5, name = (null)}
2020-01-14 20:01:19.805866+0800 Practice[30117:2521182] 3---<NSThread: 0x600002f4c5c0>{number = 4, name = (null)}
2020-01-14 20:01:19.805866+0800 Practice[30117:2521184] 2---<NSThread: 0x600002f4c580>{number = 3, name = (null)}
2020-01-14 20:01:19.805873+0800 Practice[30117:2521181] 4---<NSThread: 0x600002f5b900>{number = 6, name = (null)}
2020-01-14 20:01:19.805913+0800 Practice[30117:2521346] 5---<NSThread: 0x600002fa82c0>{number = 7, name = (null)}
2020-01-14 20:01:19.806006+0800 Practice[30117:2521131] apply---end
dispatch_apply会阻塞当前线程,直到遍历任务执行完,才会继续向下执行,遍历过程中会开启多个线程执行任务,这个方法在大的并发量中才会体现优势,上代码如下:
- (void)dispatchApply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
int flag = 150000;
NSTimeInterval start1 = [[NSDate date] timeIntervalSince1970];
dispatch_apply(flag, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSTimeInterval end1 = [[NSDate date] timeIntervalSince1970];
NSLog(@"surplus1 = %f", end1 - start1);
NSLog(@"apply---end");
NSTimeInterval start2 = [[NSDate date] timeIntervalSince1970];
for (int i = 0; i < flag; i ++) {
NSLog(@"%d---%@",i, [NSThread currentThread]);
}
NSTimeInterval end2 = [[NSDate date] timeIntervalSince1970];
NSLog(@"surplus2 = %f", end2 - start2);
}
设置大并发量flag=150000,输出时间对比:
2020-01-14 20:06:08.890703+0800 Practice[30166:2524954] surplus1 = 19.610913
2020-01-14 20:06:35.516065+0800 Practice[30166:2524954] surplus2 = 26.625173
在大并发量下,两种方式耗时时间对比比较明显
三、dispatch_barrier_async:栅栏防范,实现让一部分异步操作先执行,执行完,后面的异步操作再执行;
代码展示如下:
- (void)barrierTest {
dispatch_queue_t queue = dispatch_queue_create("net.cloudsky.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]);
});
}
日志输出:
2020-01-14 20:08:36.381930+0800 Practice[30194:2528006] 1---<NSThread: 0x600003137400>{number = 5, name = (null)}
2020-01-14 20:08:36.381930+0800 Practice[30194:2528004] 2---<NSThread: 0x6000031a5180>{number = 6, name = (null)}
2020-01-14 20:08:38.385603+0800 Practice[30194:2528006] barrier---<NSThread: 0x600003137400>{number = 5, name = (null)}
2020-01-14 20:08:40.388811+0800 Practice[30194:2528006] 3---<NSThread: 0x600003137400>{number = 5, name = (null)}
2020-01-14 20:08:40.388812+0800 Practice[30194:2528004] 4---<NSThread: 0x6000031a5180>{number = 6, name = (null)}
可以看出,barrier会让1、2的任务先执行完,再执行3、4的任务
四、dispatch_group_t: 队列组,可将执行操作的多个任务添加到队列中,再将队列添加到队列组中,可添加多个队列,分组执行任务,执行完发出通知做后面的操作;总结下来,有三种实现方式能够达到这种效果,下面一一列出:
1、dispatch_group_notify,利用这种方式在执行完一系列操作后通知主线程执行操作,示例代码如下:
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);
NSLog(@"group---end");
});
}
日志输出:
2020-01-14 20:10:24.327701+0800 Practice[30221:2529716] currentThread---<NSThread: 0x600002106140>{number = 1, name = main}
2020-01-14 20:10:24.327827+0800 Practice[30221:2529716] group---begin
2020-01-14 20:10:26.331691+0800 Practice[30221:2529783] 1---<NSThread: 0x60000213dc00>{number = 5, name = (null)}
2020-01-14 20:10:26.331737+0800 Practice[30221:2529788] 2---<NSThread: 0x600002182080>{number = 6, name = (null)}
2020-01-14 20:10:28.333223+0800 Practice[30221:2529716] 3---<NSThread: 0x600002106140>{number = 1, name = main}
2020-01-14 20:10:28.333571+0800 Practice[30221:2529716] group---end
可以看到,异步队列组中的任务全部执行完后,dispatch_group_notify接收到通知并做更新操作,如任务3的执行。
2、dispatch_group_wait
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
日志:
2020-01-14 20:12:52.973077+0800 Practice[30254:2532086] currentThread---<NSThread: 0x6000010dedc0>{number = 1, name = main}
2020-01-14 20:12:52.973219+0800 Practice[30254:2532086] group---begin
2020-01-14 20:12:54.978057+0800 Practice[30254:2532140] 1---<NSThread: 0x6000010b4580>{number = 4, name = (null)}
2020-01-14 20:12:54.978056+0800 Practice[30254:2532139] 2---<NSThread: 0x600001089cc0>{number = 5, name = (null)}
2020-01-14 20:12:54.978461+0800 Practice[30254:2532086] group---end
说明:dispatch_group_wait会阻塞当前线程,待异步队列组中的任务执行完后,才会放通向下继续执行,执行效果上跟前者类似。
3、dispatch_group_enter、dispatch_group_leave,通过这两个方法配合dispatch_async使用,也能达到队列组的效果;dispatch_group_enter、dispatch_group_leave有些类似引用计数的原理,进行+1,-1的操作,二者必须成对出现,当计数为0时,代表当前队列组中的任务已经执行完毕,可以继续向下执行;示例代码如下:
- (void)groupEnterAndLeave {
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程.
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);
NSLog(@"group---end");
});
}
日志:
2020-01-14 20:16:53.427547+0800 Practice[30298:2535216] currentThread---<NSThread: 0x600000f06e40>{number = 1, name = main}
2020-01-14 20:16:53.427682+0800 Practice[30298:2535216] group---begin
2020-01-14 20:16:55.430099+0800 Practice[30298:2535269] 1---<NSThread: 0x600000f69440>{number = 3, name = (null)}
2020-01-14 20:16:55.430109+0800 Practice[30298:2535270] 2---<NSThread: 0x600000f62740>{number = 6, name = (null)}
2020-01-14 20:16:57.431377+0800 Practice[30298:2535216] 3---<NSThread: 0x600000f06e40>{number = 1, name = main}
2020-01-14 20:16:57.431571+0800 Practice[30298:2535216] group---end
总结:实现效果上,这三种方式都能达到执行多个异步任务,最终返回刷新的效果,可根据项目实际情况选择使用。
五、关于信号量semaphore,个人理解信号量是用来控制在相同时间对同一资源的访问,有点类似加锁;
信号量常用方法如下:
dispatch_semaphore_create(1):创建信号量,并使信号量的初始值为1,即当前只能允许一个访问;当然根据初始值传入的不同,可控制当前允许访问的个数;
dispatch_semaphore_signal:使当前允许访问的个数加1;
dispatch_semaphore_wait:使当前允许访问的个数减1,当允许访问的个数为0时,后续的访问操作将无法执行,即资源不再开放访问;
常使用的场景如下:
示例1:阻塞同一线程;
- (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 = %zd",number);
}
输出日志:
020-01-14 20:19:23.123990+0800 Practice[30325:2537438] currentThread---<NSThread: 0x600002a52e40>{number = 1, name = main}
2020-01-14 20:19:23.124126+0800 Practice[30325:2537438] semaphore---begin
2020-01-14 20:19:25.127939+0800 Practice[30325:2537509] 1---<NSThread: 0x600002a39180>{number = 4, name = (null)}
2020-01-14 20:19:25.128293+0800 Practice[30325:2537438] semaphore---end,number = 100
说明:由于创建信号量时,初始值为0,程序不会等异步任务dispatch_async执行完,而是继续向下执行,当主线程执行到dispatch_semaphore_wait时被阻塞,不再向下执行,当执行完dispatch_async中的任务时,dispatch_semaphore_signal发出信号,使信号量的值+1,这时主线程会被放通不再被阻塞,继续向下执行并打印输出结果;
示例2:多并发情况下控制对同一资源的访问,保证数据的实时准确性,这点在票务系统上体现的比较明显,示例代码如下:
第一种,线程不安全的情况:
- (void)buyTicketNotSafe {
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"semaphore---begin");
self.totalTicketsCount = 50;
// queue1 代表城市1火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.cloudsky.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表城市2火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.cloudsky.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.totalTicketsCount > 0) { // 如果还有票,继续售卖
self.totalTicketsCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.totalTicketsCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { // 如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
break;
}
}
}
输出日志:
2020-01-14 20:24:59.041765+0800 Practice[30386:2542921] currentThread---<NSThread: 0x6000020c6e40>{number = 1, name = main}
2020-01-14 20:24:59.041896+0800 Practice[30386:2542921] semaphore---begin
2020-01-14 20:24:59.042142+0800 Practice[30386:2542971] 剩余票数:49 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.042158+0800 Practice[30386:2542970] 剩余票数:48 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:24:59.245586+0800 Practice[30386:2542970] 剩余票数:46 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:24:59.245609+0800 Practice[30386:2542971] 剩余票数:47 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.447427+0800 Practice[30386:2542971] 剩余票数:44 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.447435+0800 Practice[30386:2542970] 剩余票数:45 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:24:59.650666+0800 Practice[30386:2542970] 剩余票数:43 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:24:59.650677+0800 Practice[30386:2542971] 剩余票数:42 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.854681+0800 Practice[30386:2542971] 剩余票数:40 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:24:59.854697+0800 Practice[30386:2542970] 剩余票数:41 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.057841+0800 Practice[30386:2542970] 剩余票数:39 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.057841+0800 Practice[30386:2542971] 剩余票数:38 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:00.263159+0800 Practice[30386:2542970] 剩余票数:36 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.263159+0800 Practice[30386:2542971] 剩余票数:37 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:00.465090+0800 Practice[30386:2542970] 剩余票数:35 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.465110+0800 Practice[30386:2542971] 剩余票数:35 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:00.666917+0800 Practice[30386:2542970] 剩余票数:33 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.666914+0800 Practice[30386:2542971] 剩余票数:34 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:00.871344+0800 Practice[30386:2542970] 剩余票数:31 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:00.871344+0800 Practice[30386:2542971] 剩余票数:32 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.075880+0800 Practice[30386:2542970] 剩余票数:30 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:01.075880+0800 Practice[30386:2542971] 剩余票数:29 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.280835+0800 Practice[30386:2542971] 剩余票数:27 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.280843+0800 Practice[30386:2542970] 剩余票数:28 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:01.481270+0800 Practice[30386:2542970] 剩余票数:26 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:01.481270+0800 Practice[30386:2542971] 剩余票数:26 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.683215+0800 Practice[30386:2542971] 剩余票数:24 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.683215+0800 Practice[30386:2542970] 剩余票数:25 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:01.885917+0800 Practice[30386:2542971] 剩余票数:23 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:01.885924+0800 Practice[30386:2542970] 剩余票数:22 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.087981+0800 Practice[30386:2542971] 剩余票数:21 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.087987+0800 Practice[30386:2542970] 剩余票数:20 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.288467+0800 Practice[30386:2542970] 剩余票数:19 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.288471+0800 Practice[30386:2542971] 剩余票数:18 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.491659+0800 Practice[30386:2542970] 剩余票数:17 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.491670+0800 Practice[30386:2542971] 剩余票数:16 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.697167+0800 Practice[30386:2542971] 剩余票数:14 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.697167+0800 Practice[30386:2542970] 剩余票数:15 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:02.898063+0800 Practice[30386:2542971] 剩余票数:13 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:02.898063+0800 Practice[30386:2542970] 剩余票数:12 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.101597+0800 Practice[30386:2542970] 剩余票数:11 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.101614+0800 Practice[30386:2542971] 剩余票数:10 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.304368+0800 Practice[30386:2542970] 剩余票数:8 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.304368+0800 Practice[30386:2542971] 剩余票数:9 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.505029+0800 Practice[30386:2542971] 剩余票数:7 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.505035+0800 Practice[30386:2542970] 剩余票数:6 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.705613+0800 Practice[30386:2542970] 剩余票数:5 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:03.705630+0800 Practice[30386:2542971] 剩余票数:4 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.905898+0800 Practice[30386:2542971] 剩余票数:3 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:03.905908+0800 Practice[30386:2542970] 剩余票数:2 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:04.108431+0800 Practice[30386:2542971] 剩余票数:0 窗口:<NSThread: 0x6000020bcdc0>{number = 4, name = (null)}
2020-01-14 20:25:04.108429+0800 Practice[30386:2542970] 剩余票数:1 窗口:<NSThread: 0x600002045200>{number = 5, name = (null)}
2020-01-14 20:25:04.310634+0800 Practice[30386:2542970] 所有火车票均已售完
2020-01-14 20:25:04.310650+0800 Practice[30386:2542971] 所有火车票均已售完
可以看到,当多个线程访问同一资源,当资源发生改变时,数据会不准确。
第二种:线程安全:
/**
* 线程安全:使用 semaphore 加锁
* 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
*/
- (void)buyTicketSafe {
NSLog(@"currentThread---%@",[NSThread currentThread]);
NSLog(@"semaphore---begin");
semaphoreLock = dispatch_semaphore_create(1);
self.totalTicketsCount = 50;
// queue1 代表城市1火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.cloudsky.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表城市2火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.cloudsky.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.totalTicketsCount > 0) { // 如果还有票,继续售卖
self.totalTicketsCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.totalTicketsCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { // 如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
break;
}
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
}
}
日志:
2020-01-14 20:28:11.689914+0800 Practice[30418:2546067] currentThread---<NSThread: 0x600000736080>{number = 1, name = main}
2020-01-14 20:28:11.690040+0800 Practice[30418:2546067] semaphore---begin
2020-01-14 20:28:11.690368+0800 Practice[30418:2546177] 剩余票数:49 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:11.890740+0800 Practice[30418:2546560] 剩余票数:48 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:12.094728+0800 Practice[30418:2546177] 剩余票数:47 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:12.297269+0800 Practice[30418:2546560] 剩余票数:46 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:12.502602+0800 Practice[30418:2546177] 剩余票数:45 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:12.706331+0800 Practice[30418:2546560] 剩余票数:44 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:12.906836+0800 Practice[30418:2546177] 剩余票数:43 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:13.107579+0800 Practice[30418:2546560] 剩余票数:42 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:13.310564+0800 Practice[30418:2546177] 剩余票数:41 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:13.513896+0800 Practice[30418:2546560] 剩余票数:40 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:13.714300+0800 Practice[30418:2546177] 剩余票数:39 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:13.914735+0800 Practice[30418:2546560] 剩余票数:38 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:14.119221+0800 Practice[30418:2546177] 剩余票数:37 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:14.322066+0800 Practice[30418:2546560] 剩余票数:36 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:14.524925+0800 Practice[30418:2546177] 剩余票数:35 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:14.728081+0800 Practice[30418:2546560] 剩余票数:34 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:14.931475+0800 Practice[30418:2546177] 剩余票数:33 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:15.133113+0800 Practice[30418:2546560] 剩余票数:32 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:15.337582+0800 Practice[30418:2546177] 剩余票数:31 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:15.539476+0800 Practice[30418:2546560] 剩余票数:30 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:15.742918+0800 Practice[30418:2546177] 剩余票数:29 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:15.947115+0800 Practice[30418:2546560] 剩余票数:28 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:16.151334+0800 Practice[30418:2546177] 剩余票数:27 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:16.356116+0800 Practice[30418:2546560] 剩余票数:26 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:16.558461+0800 Practice[30418:2546177] 剩余票数:25 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:16.761620+0800 Practice[30418:2546560] 剩余票数:24 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:16.963740+0800 Practice[30418:2546177] 剩余票数:23 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:17.166348+0800 Practice[30418:2546560] 剩余票数:22 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:17.367036+0800 Practice[30418:2546177] 剩余票数:21 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:17.572432+0800 Practice[30418:2546560] 剩余票数:20 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:17.777916+0800 Practice[30418:2546177] 剩余票数:19 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:17.980030+0800 Practice[30418:2546560] 剩余票数:18 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:18.185497+0800 Practice[30418:2546177] 剩余票数:17 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:18.388987+0800 Practice[30418:2546560] 剩余票数:16 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:18.589401+0800 Practice[30418:2546177] 剩余票数:15 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:18.789993+0800 Practice[30418:2546560] 剩余票数:14 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:18.994816+0800 Practice[30418:2546177] 剩余票数:13 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:19.200268+0800 Practice[30418:2546560] 剩余票数:12 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:19.401382+0800 Practice[30418:2546177] 剩余票数:11 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:19.605777+0800 Practice[30418:2546560] 剩余票数:10 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:19.811006+0800 Practice[30418:2546177] 剩余票数:9 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:20.016176+0800 Practice[30418:2546560] 剩余票数:8 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:20.217251+0800 Practice[30418:2546177] 剩余票数:7 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:20.422595+0800 Practice[30418:2546560] 剩余票数:6 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:20.628031+0800 Practice[30418:2546177] 剩余票数:5 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:20.833101+0800 Practice[30418:2546560] 剩余票数:4 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:21.034921+0800 Practice[30418:2546177] 剩余票数:3 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:21.237287+0800 Practice[30418:2546560] 剩余票数:2 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:21.439123+0800 Practice[30418:2546177] 剩余票数:1 窗口:<NSThread: 0x600000766480>{number = 5, name = (null)}
2020-01-14 20:28:21.640437+0800 Practice[30418:2546560] 剩余票数:0 窗口:<NSThread: 0x6000007a8400>{number = 7, name = (null)}
2020-01-14 20:28:21.841116+0800 Practice[30418:2546177] 所有火车票均已售完
2020-01-14 20:28:21.841347+0800 Practice[30418:2546560] 所有火车票均已售完
说明:多并发情况下通过dispatch_semaphore_signal,dispatch_semaphore_wait控制信号量的信号数,实现加锁的功能,保证同一时间资源只被一个操作访问和改变,保证当前显示的票数是真实准确的,当然线程锁也能达到同样的效果;如果多并发情况下不对资源访问加以控制,同一时间多个线程访问改变同一资源,就会带来资源数据的不准确性。