前言
成长总是在不断的受挫中,反思,改进,最后成长
同步 vs 异步
同步和异步的维度是线程,区别是在当前执行的任务中,是否会阻塞当前的线程,如果是同步的会阻塞当前线程。异步的话,不会阻塞当前线程,他会开辟一个新的线程来执行该任务。
这么说,线程是用来执行任务的,队列是通过管理线程,来决定任务的执行方式。
NSOperation vs GCD
- GCD是苹果基于c语言构成的API,而NSOperation是GCD的封装
- 在NSOperationQueue,我们可以随时取消已经设定要准备执行的任务(已经开始的任务就无法阻止),而GCD停止准备执行的任务是比较困难的,没有NSOperationQueue方便
- NSOperation 能够方便的设置依赖关系,我们可以让一个NSOperation依赖另外一个NSOperation,这样的话,尽管两个NSOperation在同一个队列中,但前者直到后者执行完毕后再执行
NSOperation
NSOperation是OC层面的对外提供的面向对象的线程管理类,是基于GCD的封装。你定义想要执行的任务,他来负责调度和执行这些任务,它管理了线程,并且使线程更加高效。
- 管理线程,负责线程的创建和销毁,通过配置队列,让任务按照你想要的方式执行。
NSOperationQueue + NSOperation
NSOperation 任务,NSOperationQueue 队列,创建任务添加到队列中。队列有来决定任务的执行方式:
- 并发执行,串行执行
- 可以同时执行的并发数
- 暂停任务,开始任务
NSBlockOperation *operation = [NSBlockOperation
blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[operation start];
上面代码的意思是把operation任务添加到线程中执行。
NSBlockOperation 是一个继承NSOperation的类
addExecutionBlock
当然一个任务也不一定是一个线程在执行,我们可以通过addExecutionBlock给该任务添加多个线程来执行,当然苹果默认了addExecutionBlock添加到任务中的线程是异步的我们可以通过
if ([operation isConcurrent]) {
NSLog(@"并发");
} else {
NSLog(@"非并发");
}
来查看是非并发还是不是并发的
NSBlockOperation *operation = [NSBlockOperation
blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
// 添加多个block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次: %@",(long)i, [NSThread currentThread]);
}];
}
[operation start];
iOSThread[8646:497795] 第0次: <NSThread: 0x60000026dc40>{number = 4, name = (null)}
iOSThread[8646:497794] <NSThread: 0x60400026d840>{number = 3, name = (null)}
iOSThread[8646:497793] 第2次: <NSThread: 0x60400026db40>{number = 5, name = (null)}
iOSThread[8646:497585] 第1次: <NSThread: 0x604000068300>{number = 1, name = main}
iOSThread[8646:497793] 第4次: <NSThread: 0x60400026db40>{number = 5, name = (null)}
iOSThread[8646:497794] 第3次: <NSThread: 0x60400026d840>{number = 3, name = (null)}
可以看到addExecutionBlock,可以为该任务添加并发线程。从上面可以看出最大并发数除了主线程为还有4个,并且会把block优先分配给主线程,当主线程不在空闲时,才会选择分配到其他线程来执行。
设置代码优先级:
NSBlockOperation *downloadPic = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 ----- %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0]; // 模拟下载操作
}];
[downloadPic setQueuePriority:NSOperationQueuePriorityVeryLow]; // 设置在队列中的优先级,较低
NSBlockOperation *downloadMusic = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2 ----- %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0f]; // 模拟下载操作
}];
[downloadMusic setQueuePriority:NSOperationQueuePriorityVeryHigh];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:downloadPic];
[queue addOperation:downloadMusic];
打印代码:
iOSThread[9549:632233] 1 ----- <NSThread: 0x604000461040>{number = 3, name = (null)}
iOSThread[9549:632235] 2 ----- <NSThread: 0x60000026c240>{number = 4, name = (null)}
可以看出,队列中的任务不需要调用start来执行。而且,添加到队列中的任务在设置优先级之后,并不一定会优先执行。对于添加到 queue的Operations,执行顺序首先由已入队列的operations是否准备好,然后在根据所有operations的相对优先级确定。是否准备好由对象间的依赖关系确定,优先级等级则是operations对象本身的一个属性。默认所有operation都拥有"普通优先级",不过可以通过设置setQueuePriority方法,来降低或提升operation对象的优先级。优先级只能应用于相同queue中的operations。队列中执行任务,需要先满足依赖关系,在根据优先级来执行。
在执行的任务当中添加依赖关系
// 添加依赖
//1.任务一:下载图片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载图片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//2.任务二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//3.任务三:上传图片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上传图片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//4.设置依赖
[operation2 addDependency:operation1]; //任务二依赖任务一
[operation3 addDependency:operation2]; //任务三依赖任务二
//5.创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
队列可以有多个,任务是在加入到队列中执行的,队列中任务的执行方式是由队列来决定的,是并行队列,还是串行队列。队列与队列中的执行方式是并行的,每个队列管理这自己创建的线程。
NSInvocationOperation
这个类是继承自抽象类NSOperation的
NSInvocationOperation *operation = [[NSInvocationOperation
alloc] initWithTarget:self selector:@selector(demo) object:nil];
[operation start];
从打印结果上来看,这个类的执行是同步的,会在当前线程中执行。
GCD
GCD 提供C语言级别的线程管理调度,创建线程,销毁线程,都是由GCD来帮我们实现
GCD 提供两种队列,并发和串行队列
// 创建队列的时候可以设置该队列中任务的执行方式是串行的
DISPATCH_QUEUE_SERIAL
// 并行队列
DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t
在串行队列中,所有的任务都是以FIFO的形式执行,且当前队列中的任务执行完毕,才开始执行下一个任务。但是,相互独立的队列之间的任务的执行是可以并行的,不受另外一个队列的影响。也就是队列与队列之间任务的执行方式是独立的。
在并发队列中,任务的执行方式也是以FIFO(先进先出)的形式来执行的,只不过他不需要等待上一个任务执行结束,而且该队列会帮助我们创建新的线程来并发执行该任务。
// 主队列,串行队列
dispatch_get_main_queue()
// 全局并发队列,只能获取不能自己创建,可以为这个队列设置标识符,和对用的优先级
dispatch_get_global_queue(long identifier, unsigned long flags)
dispatch_get_main_queue()
如下面一段代码:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"执行任务");
});
把该任务加入到主队列中,同步执行
dispatch_get_global_queue(long identifier, unsigned long flags)
上面我们说了,系统提供的全局并发队列我们只可以获取但是不能够创建。我们自己创建的队列都是采用与全局队列一样的优先级。如果,我们想要设置更高优先级的队列,可以通过获取全局队列的方式,设置他的优先级从而获得更高优先级的队列:
dispatch_queue_t queue1 = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
// 高优先级
QOS_CLASS_USER_INITIATED
// 默认优先级
QOS_CLASS_DEFAULT
// 低优先级
QOS_CLASS_UTILITY
// 用户不会察觉的任务,使用他来处理预加载,或者不需要用户交互和对时间不敏感的任务。
QOS_CLASS_BACKGROUND
dispatch_set_target_queue(dispatch_object_t _Nonnull object, dispatch_queue_t _Nullable queue)
第一个参数为要设置优先级的queue,第二个参数是参照物,即将第一个queue的优先级和第二个queue的优先级设置一样
设置队列优先级
我们可以通过这个函数来设置我们自己创建队列的优先级,如下:
dispatch_queue_t testQueue = dispatch_queue_create("test1.com", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue = dispatch_queue_create("test2.com", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_set_target_queue(serialQueue, globalQueue);
dispatch_async(testQueue, ^{
NSLog(@"执行1");
});
dispatch_async(serialQueue, ^{
NSLog(@"执行2");
});
执行结果:
2017-12-17 18:22:16.766282+0800 iOSThread[14568:1310876] 执行2
2017-12-17 18:22:16.766282+0800 iOSThread[14568:1310875] 执行1
从执行结果上来看,确实设置后的优先级较高
把当前队列任务指派到其他队列中处理
dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue2, queue1);
dispatch_async(queue2, ^{
for (NSInteger i = 0; i < 20; i++) {
NSLog(@"queue2:%@, %ld", [NSThread currentThread], i);
}
});
dispatch_async(queue1, ^{
for (NSInteger i = 0; i < 20; i++) {
NSLog(@"queue1:%@, %ld", [NSThread currentThread], i);
}
});
就是把当前queue2中的任务指派到queue1中去执行,从上面代码可以看出queue1是串行队列,那么queue2中的任务就会在queue1中以串行的方式执行。
dispatch_after
dispatch_time
dispatch_time(dispatch_time_t when, int64_t delta)
第一个参数可以设置当前时间: DISPATCH_TIME_NOW
第二个参数delta表示纳秒,可以直接使用NSEC_PER_SEC
那么我们可以直接设置延时任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延时执行的任务");
});
这段代码的意思是,这个函数等待到指定的时间。然后把任务异步添加到指定队列执行
既延时2s,然后把任务添加到主队列执行
dispatch_queue_set_specific & dispatch_get_specific
dispatch_queue_set_specific 在指定队列中设定一个标识
dispatch_get_specific 在当前队列中取出标识
dispatch_group
把一组任务提交到队列当中,这些队列可以不相关,然后监听这组任务的完成
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue1 = dispatch_queue_create("test.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 30; i++) {
NSLog(@"group1 -%d - %@",i, [NSThread currentThread]);
}
});
dispatch_group_async(group, queue1, ^{
for (int i = 0; i < 30; i++) {
NSLog(@"group2 - %d - %@", i, [NSThread currentThread]);
}
});
//完成之后回调
dispatch_group_notify(group, queue, ^{
NSLog(@"完成任务 - %@",[NSThread currentThread]);
});
dispatch_barrier_async
当在并发队列中遇到一个barrier,这个方法会阻塞queue(不是阻塞当前线程)。一直等到排在这个queue前面的任务执行完后才开始执行自己,自己执行完毕后,再会取消阻塞。使这个queue中排在它后面的任务继续执行。如果你传入的是其他queue,那么就和dispatch_async一样了
dispatch_queue_t queue = dispatch_queue_create("sdk.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2.f];
NSLog(@"task 1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2.f];
});
// 等待前面的都完成,在执行 barrier 后面的
dispatch_barrier_async(queue, ^{
NSLog(@"task 2");
[NSThread sleepForTimeInterval:2.f];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3.f];
NSLog(@"task 3");
});
dispatch_async(queue, ^{
NSLog(@"task 4");
});
2017-12-18 21:51:09.001390+0800 iOSThread[37907:2827277] task 1
2017-12-18 21:51:09.001733+0800 iOSThread[37907:2827277] task 2
2017-12-18 21:51:11.005180+0800 iOSThread[37907:2827274] task 4
2017-12-18 21:51:14.008395+0800 iOSThread[37907:2827277] task 3
dispatch_barrier_sync
这个方法的使用和上一个一样,传入自定义的并发队列DISPATCH_QUEUE_CONCURRENT,它和上一个方法一样的阻塞queue,不同的是,这个方法还会阻塞当前线程。如果,你传入的是其他的queue,那么就和 "dispatch_sync" 是一样的了。
造成死锁的几个例子
NSLog(@"之前 - %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"sync - %@",[NSThread currentThread]);
});
NSLog(@"之后 - %@",[NSThread currentThread]);
这段代码的执行是,先打印第一句,然后阻塞当前线程,把block放到 main_queue中。可以main_queue中的任务会被取出来放到主线程中执行,但是主线程已经被阻塞。这样就造成了一个死锁。
另外一个例子:
dispatch_queue_t queue = dispatch_queue_create("test.com", DISPATCH_QUEUE_SERIAL);
NSLog(@"之前 - %@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"sync之前 - %@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"sync - %@",[NSThread currentThread]);
});
NSLog(@"sync - 之后%@",[NSThread currentThread]);
});
NSLog(@"之后 - %@",[NSThread currentThread]);
1.使用 DISPATCH_QUEUE_SERIAL 这个参数,创建一个串行的队列。
2.在 dispatch_async 异步执行,所以当前线程不会被阻塞,于是就有了两条线程。一条当前线程继续往下打印"之后 - ",而另外一条线程执行block中的内容打印 "sync之前 - ",这句。因为两条是并行的所以打印的先后顺序无所谓。
3.dispatch_sync同步执行,于是他把当前的线程阻塞,一直等到sync里的任务执行完才会继续往下。于是,sync就高兴的把自己Block中的任务放到queue中,可谁想queue是一个串行队列,一次执行一个任务。所以queue必须等到当前的任务执行完毕,但是queue又被阻塞了,于是就发生死锁。
网友评论