一、概述
多线程任务管理,基于C
语言的底层API
,采用闭包形式与外界通讯,代码简洁高效;充分利用多核CPU
,自动管理线程的生命周期,我们只负责任务的创建。
二、队列和任务
1、队列
常用的数据结构之一,具有先进先出(FIFO
)的特性,是一种受限制的线性表,只允许在表头删除任务,在表尾部插入。队列的这种特点生活中很常见,例如学校餐厅排队就餐,银行取号排队办理业务等,先到先得,后来的要在队尾排队。
1.1、在GCD
中队列分为串行队列和并行队列。
串行队列:在同一个队列中,一个任务结束后才能创建新的线程并执行任务。
并行队列:在同一个队列中,同时创建多个线程,并行执行任务。
例如行车路上,道路狭窄,车辆需要前后排列依次通行,这时我们叫串行队列,在100米宽的道路上,车辆可以并排行进这时我们叫并行队列。
1.2、创建队列
创建队列使用dispatch_queue_create
方法。
dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
label
:队列标签,队列编号
Attr
:标识队列类型,串行还是并行
创建串行队列:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
创建并行队列:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
1.3、获取队列
除创建队列外,还可以获取系统中默认存在的两个队列:主队列和全局队列。
主队列:串行队列,主队列中的任务都会放在主线程中执行。获取主线程队列:
dispatch_queue_t queue =dispatch_get_main_queue()
全局队列:又称全局并行队列。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一个参数:队列优先级,范围如下:
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
一般使用DISPATCH_QUEUE_PRIORITY_DEFAULT
第二个参数:标识位置0即可
2、任务
任务有同步任务与异步任务,同步任务直接加入到主线程中执行,异步任务会创建新的线程。
GCD
提供了同步任务创建方法和异步任务创建方法。
同步任务:
dispatch_sync(queue, ^{
//同步任务执行代码
});
异步任务:
dispatch_async(queue, ^{
//异步任务执行代码
});
三、队列任务的使用
任何任务都要放在队列中执行,以上有两种类型的队列,有两种类型的任务,因此队列与任务有四种不同组合:
并行队列+同步任务
并行队列+异步任务
串行队列+同步任务
串行队列+异步任务
除开发人员创建的队列外还存在串行主队列与并行全局队列,而全局并行队列可以作为普通并行队列使用,因此又产生了两种任务队列组合:
主队列+同步任务
主队列+异步任务
1、并行队列+同步任务
并行队列允许同时创建多个线程,同步任务不会创建新的线程,直接加入到主线程中。
验证代码如下:
/**********并行队列+同步任务**********/
-(void)concurrent_sync{
NSLog(@"%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("queue_yahibo", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue1->i:%d",i);//打印任务
}
});
dispatch_sync(queue, ^{
NSLog(@"queue2:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue2->i:%d",i);//打印任务
}
});
dispatch_sync(queue, ^{
NSLog(@"queue3:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue3->i:%d",i);//打印任务
}
});
}
执行结果:
2019-07-05 15:23:33.581267+0800 GCD2[26698:412634] queue1:<NSThread: 0x1005063c0>{number = 1, name = main}
2019-07-05 15:23:33.685574+0800 GCD2[26698:412634] queue1->i:0
2019-07-05 15:23:33.790259+0800 GCD2[26698:412634] queue1->i:1
2019-07-05 15:23:33.892951+0800 GCD2[26698:412634] queue1->i:2
2019-07-05 15:23:33.893122+0800 GCD2[26698:412634] queue2:<NSThread: 0x1005063c0>{number = 1, name = main}
2019-07-05 15:23:33.996298+0800 GCD2[26698:412634] queue2->i:0
2019-07-05 15:23:34.098246+0800 GCD2[26698:412634] queue2->i:1
2019-07-05 15:23:34.201317+0800 GCD2[26698:412634] queue2->i:2
2019-07-05 15:23:34.201488+0800 GCD2[26698:412634] queue3:<NSThread: 0x1005063c0>{number = 1, name = main}
2019-07-05 15:23:34.305725+0800 GCD2[26698:412634] queue3->i:0
2019-07-05 15:23:34.407814+0800 GCD2[26698:412634] queue3->i:1
2019-07-05 15:23:34.511323+0800 GCD2[26698:412634] queue3->i:2
总结:打印任务都是在主线程中执行,没有开启新线程,任务顺序执行。同步任务不创建新线程尽管并行队列允许。
2、并行队列+异步任务
允许创建多个线程,同时执行任务。
/**********并行队列+异步任务**********/
-(void)concurrent_async{
NSLog(@"%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("aqueue_yahibo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue1->i:%d",i);//打印任务
}
});
dispatch_async(queue, ^{
NSLog(@"queue2:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue2->i:%d",i);//打印任务
}
});
dispatch_async(queue, ^{
NSLog(@"queue3:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue3->i:%d",i);//打印任务
}
});
}
执行结果:
2019-07-05 15:44:40.541135+0800 GCD2[26918:423494] queue1:<NSThread: 0x600002ca6d40>{number = 4, name = (null)}
2019-07-05 15:44:40.541153+0800 GCD2[26918:423496] queue2:<NSThread: 0x600002c99680>{number = 3, name = (null)}
2019-07-05 15:44:40.541167+0800 GCD2[26918:423504] queue3:<NSThread: 0x600002c88e80>{number = 5, name = (null)}
2019-07-05 15:44:40.643411+0800 GCD2[26918:423504] queue3->i:0
2019-07-05 15:44:40.643411+0800 GCD2[26918:423494] queue1->i:0
2019-07-05 15:44:40.643411+0800 GCD2[26918:423496] queue2->i:0
2019-07-05 15:44:40.743625+0800 GCD2[26918:423496] queue2->i:1
2019-07-05 15:44:40.743625+0800 GCD2[26918:423494] queue1->i:1
2019-07-05 15:44:40.743645+0800 GCD2[26918:423504] queue3->i:1
2019-07-05 15:44:40.846249+0800 GCD2[26918:423494] queue1->i:2
2019-07-05 15:44:40.846269+0800 GCD2[26918:423504] queue3->i:2
2019-07-05 15:44:40.846279+0800 GCD2[26918:423496] queue2->i:2
总结:先一次创建线程3 4 5,后交替执行任务(可理解为任务在同时进行)。线程创建耗时远小于任务耗时。
3、串行队列+同步任务
依次创建线程,依次执行任务,同步任务不会创建新的线程,直接加入到主线程中。
/**********串行队列+异步任务**********/
-(void)serial_sync{
NSLog(@"%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("aqueue_yahibo", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue1->i:%d",i);//打印任务
}
});
dispatch_sync(queue, ^{
NSLog(@"queue2:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue2->i:%d",i);//打印任务
}
});
dispatch_sync(queue, ^{
NSLog(@"queue3:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue3->i:%d",i);//打印任务
}
});
}
总结:和并行队列+同步任务一样有同步任务就不会创建新的线程,在主线程中依次执行。
4、串行队列+异步任务
一次只允许创建一个线程,异步任务开辟新的线程,因此开辟新线程,顺序执行任务。
/**********串行队列+同步任务**********/
-(void)serial_async{
NSLog(@"%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("aqueue_yahibo", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue1->i:%d",i);//打印任务
}
});
dispatch_async(queue, ^{
NSLog(@"queue2:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue2->i:%d",i);//打印任务
}
});
dispatch_async(queue, ^{
NSLog(@"queue3:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue3->i:%d",i);//打印任务
}
});
}
执行结果:
2019-07-05 16:15:21.689644+0800 GCD2[27190:438198] queue1:<NSThread: 0x600003186200>{number = 3, name = (null)}
2019-07-05 16:15:21.789806+0800 GCD2[27190:438198] queue1->i:0
2019-07-05 16:15:21.890003+0800 GCD2[27190:438198] queue1->i:1
2019-07-05 16:15:21.990909+0800 GCD2[27190:438198] queue1->i:2
2019-07-05 16:15:21.991172+0800 GCD2[27190:438198] queue2:<NSThread: 0x600003186200>{number = 3, name = (null)}
2019-07-05 16:15:22.095481+0800 GCD2[27190:438198] queue2->i:0
2019-07-05 16:15:22.199444+0800 GCD2[27190:438198] queue2->i:1
2019-07-05 16:15:22.301926+0800 GCD2[27190:438198] queue2->i:2
2019-07-05 16:15:22.302171+0800 GCD2[27190:438198] queue3:<NSThread: 0x600003186200>{number = 3, name = (null)}
2019-07-05 16:15:22.402858+0800 GCD2[27190:438198] queue3->i:0
2019-07-05 16:15:22.507079+0800 GCD2[27190:438198] queue3->i:1
2019-07-05 16:15:22.609571+0800 GCD2[27190:438198] queue3->i:2
总结:创建新的线程3
,因为顺序执行一个任务结束后线程被回收,下一个任务执行前创建的线程number
依然是3
。
5、主队列+同步任务(死锁)
主线程的特点:主线程会先执行主线程上的代码段(代码段结束),然后才执行主队列上的任务。
同步执行函数dispatch_sync
特点:等待任务执行完成才结束。
这样主线程等待代码段返回,才执行任务,而代码段dispatch_sync
要等任务完成才能返回。构成了相互等待,造成死锁。
运行代码:
/**********主队列+同步任务**********/
-(void)main_sync{
NSLog(@"%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue1->i:%d",i);//打印任务
}
});
}
执行崩溃。
wait.png6、其他线程中执行主队列+同步任务(解锁)
注意在其他线程中执行主线程+同步队列
,不会造成死锁,原因是main+sync
放入到其他线程,而其他线程任务被放在主线程,这时候主线程不需要等待同步代码段执行结束,而去直接处理任务。任务处理完成,同步代码段也就执行完成,打破之前的死锁。代码如下:
/**********主队列+同步任务 在其他线程中执行**********/
-(void)main_sync{
dispatch_queue_t queue = dispatch_queue_create("queue_yahibo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue1->i:%d",i);//打印任务
}
});
});
}
执行结果:
2019-07-05 17:31:58.821279+0800 GCD2[27883:509852] <NSThread: 0x600001d97b80>{number = 3, name = (null)}
2019-07-05 17:31:58.833359+0800 GCD2[27883:509700] queue1:<NSThread: 0x600001dc6940>{number = 1, name = main}
2019-07-05 17:31:58.933592+0800 GCD2[27883:509700] queue1->i:0
2019-07-05 17:31:59.034285+0800 GCD2[27883:509700] queue1->i:1
2019-07-05 17:31:59.135283+0800 GCD2[27883:509700] queue1->i:2
7、串行队列+异步任务嵌套同步任务(死锁)
串行队列异步任务中嵌套同步任务同样会出现死锁问题。
/**********串行队列+异步任务嵌套同步任务(死锁)**********/
-(void)main_sync{
dispatch_queue_t queue = dispatch_queue_create("queue_yahibo", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"queue1:%@",[NSThread currentThread]);//打印当前线程
for (int i=0; i<3; i++) {
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"queue1->i:%d",i);//打印任务
}
});
});
}
- 串行特点:等待任务完成执行队列其他任务
- 串行异步任务
dispatch_async
等待内部任务dispatch_sync
结束 - 串行同步任务
dispatch_sync
等待上一级任务dispatch_async
结束 - dispatch_async和dispatch_sync相互等待构成死锁
四、GCD其他应用
1、单例,特性在整个进程运行期间只创建一次,对象唯一。
使用dispatch_once
:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
dispatch_once
代码块中代码只执行一次。
2、延时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延时1.0秒执行任务");
});
其他延时:
[self performSelector:@selector(task) withObject:nil afterDelay:1.0];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
3、栅栏函数
等待队列任务执行完成才执行后面的函数,只能在并行队列中使用。
dispatch_barrier_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"我是一个栅栏函数");
});
4、GCD定时器,不受Runloop影响
__block int time = 0;
//创建一个定时器,time必须长期持有,否则代码执行完就被释放
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0*NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
time++;
NSLog(@"计时:%d",time);
});
dispatch_resume(timer);
5、GCD队列组dispatch_group_t
多个任务执行完后,才统一返回主线程处理:
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"我是任务1");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"我是任务2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"任务一任务二都执行完成");
});
NSLog(@"end");
执行结果:
2019-07-05 18:51:42.829144+0800 GCD2[31804:1055905] end
2019-07-05 18:51:42.933168+0800 GCD2[31804:1055937] 我是任务2
2019-07-05 18:51:42.933421+0800 GCD2[31804:1055936] 我是任务1
2019-07-05 18:51:43.033894+0800 GCD2[31804:1055905] 任务一任务二都执行完成
比如空间上传图片,上传完成后回调通知主线程已完成。下载视频,打包处理等需要。
6、dispatch_group_wait
暂停当前线程,当前group
执行完成才继续要下执行。
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"我是任务1");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"我是任务2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"任务一任务二都执行完成");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"end");
执行结果:
2019-07-05 18:50:47.440209+0800 GCD2[31779:1054875] 我是任务2
2019-07-05 18:50:47.543378+0800 GCD2[31779:1054909] 我是任务1
2019-07-05 18:50:47.543378+0800 GCD2[31779:1054911] end
2019-07-05 18:50:47.644511+0800 GCD2[31779:1054875] 任务一任务二都执行完成
对比上面运行结果,end
在任务完成之后才执行,卡在wait
后。
7、dispatch_group_enter、dispatch_group_leave
表示进入的任务和离开的任务,dispatch_group_enter
和dispatch_group_leave
成对出现,当进入的个数等于离开任务的个数才会解除dispatch_group_wait
阻塞,执行dispatch_group_notify
函数。类似于操作系统上的pv
操作,对信号量做加减操作,只有最终信号量为0时,解除阻塞。
dispatch_group_enter
: 任务忙(任务进行中)计数加一;
dispatch_group_leave
: 任务结束计数减一。
下面看一下只做计数加一的操作:
dispatch_group_enter(group);//任务计数加一
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"我是任务1");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"我是任务2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"任务一任务二都执行完成");
});
NSLog(@"end”);
打印结果:
2019-07-08 19:37:17.857429+0800 GCD2[993:19404] end
2019-07-08 19:37:17.960956+0800 GCD2[993:19446] 我是任务2
2019-07-08 19:37:17.960963+0800 GCD2[993:19445] 我是任务1
任务执行完后未能进入到dispatch_group_notify
中,为阻塞态,需要在任务结束后做任务计数减一操作。如下代码:
dispatch_group_enter(group);//任务计数加一
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"我是任务1");
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"我是任务2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:0.1];//模拟耗时操作
NSLog(@"任务一任务二都执行完成");
});
NSLog(@"end");
打印结果:
2019-07-08 19:43:49.960294+0800 GCD2[1074:22837] end
2019-07-08 19:43:50.062250+0800 GCD2[1074:22890] 我是任务1
2019-07-08 19:43:50.062253+0800 GCD2[1074:22889] 我是任务2
2019-07-08 19:43:50.162760+0800 GCD2[1074:22837] 任务一任务二都执行完成
任务结束计数减一后,阻塞立刻解除。
8、线程安全
多个线程读写同一个或一组数据,如果数据变化的结果和单线程是一致的我们就说线程是安全的,如果不一致则线程不安全,我们也叫原子性(线程安全)非原子性(线程不安全)。
一般后台设计到的线程安全会相对多一点,比如对商品库存数减一,首先要加锁,排队操作,比如两个人同时下单,如果没有加锁会出现,在两个线程中同时做stock-1
操作,这时的stock
为100
,则最终得到的结果都是99
,被插入到数据库,明明一百件确出来了200
、300
的订单,库管顿时就上火了。下面就模拟一下多线程库存操作。
非原子操作:需要在不同组创建多个线程
//非线程安全
-(void)semaphore{
self.stock = 100;
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_queue_create("stock_queue1", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"%@",[NSThread currentThread]);
[weakSelf reduceStock];
});
dispatch_async(dispatch_queue_create("stock_queue2", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"%@",[NSThread currentThread]);
[weakSelf reduceStock];
});
}
//减库存
-(void)reduceStock{
static int num = 0;//记录减库存次数,理论要求num=50
while (self.stock>0) {
if (self.stock>0) {
num++;//减库存次数
self.stock--;//实际库存操作
NSLog(@"stock:%d",self.stock);
[NSThread sleepForTimeInterval:0.01];//模拟耗时操作
}else{
NSLog(@"库存为零");
break;
}
}
NSLog(@"减操作:%d次",num);
}
截取一部分打印结果:
2019-07-08 20:37:54.562600+0800 GCD2[1780:51291] stock:97
2019-07-08 20:37:54.665220+0800 GCD2[1780:51291] stock:95
2019-07-08 20:37:54.665220+0800 GCD2[1780:51290] stock:95
2019-07-08 20:37:54.769379+0800 GCD2[1780:51290] stock:94
2019-07-08 20:37:54.769379+0800 GCD2[1780:51291] stock:94
……
2019-07-08 20:37:59.810830+0800 GCD2[1780:51291] num:106
2019-07-08 20:37:59.915351+0800 GCD2[1780:51290] num:106
发现以上打印结果,出现库存未正常减一,库存减操作共106
次,实际库存100
,与实际要求不符,因此该操作是非线程安全的。不可在实际应用中使用。
线程安全操作:添加semaphore
锁
//线程安全
-(void)semaphore{
self.stock = 100;
semaphore_lock = dispatch_semaphore_create(1);
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_queue_create("stock_queue1", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"%@",[NSThread currentThread]);
[weakSelf reduceStock];
});
dispatch_async(dispatch_queue_create("stock_queue2", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"%@",[NSThread currentThread]);
[weakSelf reduceStock];
});
}
//减库存
-(void)reduceStock{
static int num = 0;//记录减库存次数,理论要求num=50
while (self.stock>0) {
dispatch_semaphore_wait(semaphore_lock, DISPATCH_TIME_FOREVER);
if (self.stock>0) {
num++;//减库存次数
self.stock--;//实际库存操作
NSLog(@"stock:%d",self.stock);
[NSThread sleepForTimeInterval:0.01];//模拟耗时操作
dispatch_semaphore_signal(semaphore_lock);
}else{
NSLog(@"库存为零");
dispatch_semaphore_signal(semaphore_lock);
break;
}
}
NSLog(@"减操作:%d次",num);
}
打印结果:
019-07-08 20:57:36.846273+0800 GCD2[2087:63260] stock:99
……
2019-07-08 20:57:36.857036+0800 GCD2[2087:63259] stock:1
2019-07-08 20:57:36.867285+0800 GCD2[2087:63260] stock:0
2019-07-08 20:57:36.878032+0800 GCD2[2087:63259] 库存为零
2019-07-08 20:57:36.878031+0800 GCD2[2087:63260] 减操作:100次
2019-07-08 20:57:36.878163+0800 GCD2[2087:63259] 减操作:100次
数据正常,对stock
的操作是按顺序执行的,100
库存只减100
次,符合实际需求。库管大哥再也不用慌张了。
提到信号量补充一个对象锁NSLock
NSLock
:对象锁,保证线程安全。加锁的目的和信号量一样,保证线程安全,保证数据异步操作结果和同步操作的结果一致。
代码如下:
/*
NSLock:对象锁,两个或多个线程对一个对象进行访问时需要需要对对象加锁
*/
-(void)lock{
NSLock *lock = [[NSLock alloc] init];
//创建一个异步任务
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
sleep(2);
NSLog(@"任务一");
[lock unlock];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
NSLog(@"任务二");
[lock unlock];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
NSLog(@"任务三");
[lock unlock];
});
}
执行结果:
2019-07-12 22:14:53.902804+0800 GCD2[2441:52502] 任务一
2019-07-12 22:14:53.903090+0800 GCD2[2441:52504] 任务二
2019-07-12 22:14:53.903246+0800 GCD2[2441:52503] 任务三
以上在任务一未解锁前,任务二前的加锁操作被阻塞,等待任务一解锁,解锁后立刻往下执行,任务三也是如此。
在不加锁的情况下,去掉代码中的对象锁得到的结果如下:
2019-07-12 22:23:29.414548+0800 GCD2[2540:56525] 任务三
2019-07-12 22:23:29.414548+0800 GCD2[2540:56523] 任务一
2019-07-12 22:23:29.414550+0800 GCD2[2540:56526] 任务二
任务执行是同步进行的,所以谁先完成不确定。
如果同时操作一个对象属性,在未赋值前都拿到了属性值num=0
,这时对数据做+1
运算,那么三次运算后得到的最终结果是1
,则该情况下线程是不安全的
代码如下:
-(void)nolock{
__block int num = 0;
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//创建一个异步任务
dispatch_group_async(group, queue, ^{
//模拟取属
int tmp = num;
sleep(1);//模拟耗时操作
num = tmp+1;
NSLog(@"任务一");
});
dispatch_group_async(group, queue, ^{
int tmp = num;
sleep(1);//模拟耗时操作
num = tmp+1;
NSLog(@"任务二");
});
dispatch_group_async(group, queue, ^{
int tmp = num;
sleep(1);//模拟耗时操作
num = tmp+1;
NSLog(@"任务三");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"num:%d",num);//结果必须是1
});
}
打印结果:
2019-07-12 22:40:25.545517+0800 GCD2[2774:65690] 任务二
2019-07-12 22:40:25.545536+0800 GCD2[2774:65692] 任务一
2019-07-12 22:40:25.545496+0800 GCD2[2774:65691] 任务三
2019-07-12 22:40:25.545992+0800 GCD2[2774:65692] num:1
执行结果和预期不一致,则说线程是不安全的,在实际应用中任务执行耗时是不同的,所以执行结果也是不确定的。
线程加锁如下:
-(void)lock{
__block int num = 0;
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLock *lock = [[NSLock alloc] init];
//创建一个异步任务
dispatch_group_async(group, queue, ^{
[lock lock];
//模拟取属
int tmp = num;
sleep(1);//模拟耗时操作
num = tmp+1;
NSLog(@"任务一");
[lock unlock];
});
dispatch_group_async(group, queue, ^{
[lock lock];
int tmp = num;
sleep(1);//模拟耗时操作
num = tmp+1;
NSLog(@"任务二");
[lock unlock];
});
dispatch_group_async(group, queue, ^{
[lock lock];
int tmp = num;
sleep(1);//模拟耗时操作
num = tmp+1;
NSLog(@"任务三");
[lock unlock];
});
dispatch_group_notify(group, queue, ^{
NSLog(@"num:%d",num);//结果必须是一
});
}
输出:
2019-07-12 22:56:20.602732+0800 GCD2[3017:73942] 任务一
2019-07-12 22:56:21.607104+0800 GCD2[3017:73940] 任务二
2019-07-12 22:56:22.610856+0800 GCD2[3017:73941] 任务三
2019-07-12 22:56:22.611203+0800 GCD2[3017:73941] num:3
这时候对数据操作是顺序执行的,结果为预想结果,线程安全。
网友评论