学习线程之前我们先温习下
多线程的原理:同一时间,cpu只能处理一条线程,只有一条线程在工作,多线程并发执行,其实是cpu快速的在多条线程间切换。
优点:1、提高程序的执行效率
2、提高cpu的利用率
缺点: 线程多了就会消耗大量的cpu资源,降低程序性能,线程调度频率也会降低,同时耗电。
一、 NSTread介绍
是苹果官方提供的,使用起来比 pthread 更加面向对象,简单易用,可以直接操作线程对象。不过也需要需要程序员自己管理线程的生命周期。
//使用target对象的selector作为线程的任务执行体,该selector方法最多可以接收一个参数,该参数即为argument
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
//使用block作为线程的任务执行体
- (instancetype)initWithBlock:(void (^)(void))block
/*
类方法,返回值为void
使用一个block作为线程的执行体,并直接启动线程
上面的实例方法返回NSThread对象需要手动调用start方法来启动线程执行任务
*/
+ (void)detachNewThreadWithBlock:(void (^)(void))block
/*
类方法,返回值为void
使用target对象的selector作为线程的任务执行体,该selector方法最多接收一个参数,该参数即为argument
同样的,该方法创建完县城后会自动启动线程不需要手动触发
*/
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
NSTread子线程例子
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(firstThread:) object:@"Hello, World"];
//设置线程的名字,方便查看
[thread setName:@"firstThread"];
//启动线程
[thread start];
}
//线程的任务执行体并接收一个参数arg
- (void)firstThread:(id)arg
{
NSLog(@"Task %@ %@", [NSThread currentThread], arg);
NSLog(@"Thread Task Complete");
}
常见API
// 获得主线程
+ (NSThread *)mainThread;
// 判断是否为主线程(对象方法)
- (BOOL)isMainThread;
// 判断是否为主线程(类方法)
+ (BOOL)isMainThread;
// 获得当前线程
NSThread *current = [NSThread currentThread];
// 线程的名字——setter方法
- (void)setName:(NSString *)n;
// 线程的名字——getter方法
- (NSString *)name;
线程状态控制方法
// 线程进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
- (void)start;
// 线程进入阻塞状态
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//强制停止线程 线程进入死亡状态
+ (void)exit;
线程间的通讯
// 在主线程上执行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
// equivalent to the first method with kCFRunLoopCommonModes
// 在指定线程上执行操作
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// 在当前线程上执行操作,调用 NSObject 的 performSelector:相关方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
二、GCD Dispatch
GCD 可用于多核的并行运算
GCD 会自动利用更多的 CPU 内核(比如双核、四核)
GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
GCD任务和队列
GCD 中两个核心概念:任务和队列
任务
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的
-
同步执行(sync)
- 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
- 只能在当前线程中执行任务,
不具备
开启新线程的能力
-
异步执行(async)
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
- 可以在新的线程中执行任务,
具备
开启新线程的能力。
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
队列
队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务
串行队列(Serial Dispatch Queue)
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
并发队列的并发功能只有在异步(dispatch_async)函数下才有效
队列的创建方法/获取方法
可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
对于串行队列,GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)
所有放在主队列中的任务,都会放到主线程中执行
可使用dispatch_get_main_queue()获得主队列。
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
对于并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)
可以使用dispatch_get_global_queue来获取。需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
GCD 的基本使用
同步串行队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
NSLog(@"1---%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
NSLog(@"2---%@",[NSThread currentThread]);
}
});
2018-12-03 15:58:42.790220+0800 001--Block[4215:271492] 1---{number = 1, name = main}
2018-12-03 15:58:42.790508+0800 001--Block[4215:271492] 1---{number = 1, name = main}
2018-12-03 15:58:42.790670+0800 001--Block[4215:271492] 2---{number = 1, name = main}
2018-12-03 15:58:42.790809+0800 001--Block[4215:271492] 2---{number = 1, name = main}
根据打印结果可知,同步串行队列即没有开启新的线程,也没有异步执行
同步并行队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
NSLog(@"1---%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
NSLog(@"2---%@",[NSThread currentThread]);
}
});
2018-12-03 16:00:13.963915+0800 001--Block[4250:273199] 1---{number = 1, name = main}
2018-12-03 16:00:13.964146+0800 001--Block[4250:273199] 1---{number = 1, name = main}
2018-12-03 16:00:13.964329+0800 001--Block[4250:273199] 2---{number = 1, name = main}
2018-12-03 16:00:13.964605+0800 001--Block[4250:273199] 2---{number = 1, name = main}
根据两种打印我们发现:同步函数既不会开启新的线程,也不会执行并发任务
异步串行队列
NSLog(@"主线程:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1====%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2====%@",[NSThread currentThread]); // 打印当前线程
}
});
2018-12-03 16:06:46.425846+0800 001--Block[4371:279078] 主线程:{number = 1, name = main}
2018-12-03 16:06:46.426329+0800 001--Block[4371:279184] 1===={number = 3, name = (null)}
2018-12-03 16:06:46.426596+0800 001--Block[4371:279184] 1===={number = 3, name = (null)}
2018-12-03 16:06:46.426860+0800 001--Block[4371:279184] 2===={number = 3, name = (null)}
2018-12-03 16:06:46.427033+0800 001--Block[4371:279184] 2===={number = 3, name = (null)}
结果:有开启新的线程,串行执行任务
异步并行队列
NSLog(@"主线程:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1====%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2====%@",[NSThread currentThread]); // 打印当前线程
}
});
2018-12-03 16:04:29.192038+0800 001--Block[4310:276444] 主线程:{number = 1, name = main}
2018-12-03 16:04:29.192416+0800 001--Block[4310:276688] 1===={number = 3, name = (null)}
2018-12-03 16:04:29.192436+0800 001--Block[4310:276690] 2===={number = 4, name = (null)}
2018-12-03 16:04:29.192603+0800 001--Block[4310:276688] 1===={number = 3, name = (null)}
2018-12-03 16:04:29.192609+0800 001--Block[4310:276690] 2===={number = 4, name = (null)}
结果:有开启新的线程,并发执行任务。想要出现明显的并发执行效果,可以sleep
死锁问题
【问题1】
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
输出:2018-12-03 16:21:09.388970+0800 001--Block[4468:285688] 执行任务1
(lldb)
死锁,dispatch_sync与main_queue造成死锁,任务3执行结束才会执行任务2;而主线程中是执行完sync才会执行任务3,造成互相等待死锁。
修改成异步,就不会死锁了,如下
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
【问题2】
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
输出:
2018-12-03 16:24:08.183248+0800 001--Block[4517:288411] 执行任务1
2018-12-03 16:24:08.183479+0800 001--Block[4517:288411] 执行任务5
2018-12-03 16:24:08.183497+0800 001--Block[4517:288659] 执行任务2
(lldb)
看打印是死锁了,执行任务3和执行任务4之间造成死锁。
原因:dispatch_async进来是串行,NSLog(@"执行任务2");、dispatch_sync、NSLog(@"执行任务4");需要顺序执行,dispatch_sync同步阻塞当前线程
修改
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // 0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // 1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
看一些案例:
- 同步并发嵌套异步
- (void)syncConcurrentasync{
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("com.gcd", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{//block1
NSLog(@"1");
dispatch_async(queue, ^{//block2
NSLog(@"2");
});
// sleep(1);
NSLog(@"3");
});
NSLog(@"end");
}
2020-12-25 13:28:18.675262+0800 JeyRAC[77995:2073956] begin
2020-12-25 13:28:18.675379+0800 JeyRAC[77995:2073956] 1
2020-12-25 13:28:18.675479+0800 JeyRAC[77995:2073956] 3
2020-12-25 13:28:18.675493+0800 JeyRAC[77995:2074038] 2
2020-12-25 13:28:18.675551+0800 JeyRAC[77995:2073956] end
如果把sleep(1)放开
2020-12-25 13:29:08.428569+0800 JeyRAC[78025:2074874] begin
2020-12-25 13:29:08.428684+0800 JeyRAC[78025:2074874] 1
2020-12-25 13:29:08.428800+0800 JeyRAC[78025:2075061] 2
2020-12-25 13:29:09.429837+0800 JeyRAC[78025:2074874] 3
2020-12-25 13:29:09.430007+0800 JeyRAC[78025:2074874] end
总结:begin打印,此时是主线程中,同步任务block1,阻塞主线程,打印1,block2异步开启新线程,继续往下走,打印3,此时子线程block2走完,打印2.
加了sleep(1),block2里面的子线程时间是不可控的,可能比3先打印
- //同步并发嵌套同步
- (void)syncConcurrentsync{
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("com.gcd", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{//block1
NSLog(@"1");
dispatch_sync(queue, ^{//block2
sleep(2);
NSLog(@"2");
});
NSLog(@"3");
});
NSLog(@"end");
}
2020-12-25 13:36:51.089937+0800 JeyRAC[78207:2082457] begin
2020-12-25 13:36:51.090042+0800 JeyRAC[78207:2082457] 1
2020-12-25 13:36:53.091042+0800 JeyRAC[78207:2082457] 2
2020-12-25 13:36:53.091170+0800 JeyRAC[78207:2082457] 3
2020-12-25 13:36:53.091251+0800 JeyRAC[78207:2082457] end
同步任务会阻塞当前线程一步一步往下走就对了
- 同步串行嵌套异步步
- (void)syncSerialasync{
NSLog(@"begin");
dispatch_queue_t queue = dispatch_queue_create("com.gcd", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{//block1
NSLog(@"1");
dispatch_async(queue, ^{//block2
sleep(2);
NSLog(@"2");
});
dispatch_async(queue, ^{//block3
NSLog(@"4");
});
sleep(2);
NSLog(@"3");
});
// sleep(5);
NSLog(@"end");
}
2020-12-25 13:54:04.277575+0800 JeyRAC[78748:2101142] begin
2020-12-25 13:54:04.277679+0800 JeyRAC[78748:2101142] 1
2020-12-25 13:54:04.277778+0800 JeyRAC[78748:2101142] 3
2020-12-25 13:54:04.277834+0800 JeyRAC[78748:2101142] end
2020-12-25 13:54:06.280145+0800 JeyRAC[78748:2101348] 2
2020-12-25 13:54:06.280277+0800 JeyRAC[78748:2101348] 4
总结:进来dispatch_sync是同步,打印1。串行队列block1中包含NSLog(@"1");、block2、block3、NSLog(@"3");,串行队列block1是要执行完才会往下执行,所以打印3了才会继续执行子线程。
sleep(5)放开的话,2,4会在end前打印,异步跟这里就没什么关系了
- 主队列中异步:不会开辟新线程,不会阻塞当前线程
主队列中添加的任务需要等主线中的任务执行,再执行
- (void)main{
NSLog(@"begin");
dispatch_queue_t mainq = dispatch_get_main_queue();
dispatch_async(mainq, ^{//block
NSLog(@"1");
});
sleep(2);
NSLog(@"end");
}
2020-12-25 14:46:37.602158+0800 JeyRAC[80011:2142108] begin
2020-12-25 14:46:39.603256+0800 JeyRAC[80011:2142108] end
2020-12-25 14:46:39.621192+0800 JeyRAC[80011:2142108] 1
拓展方法
dispatch_apply的循环
- (void)apply {
dispatch_queue_t queue = dispatch_queue_create("dd", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i< 10; i++) {
dispatch_async(queue, ^{
NSLog(@"a-index==%d----线程%@", i, [NSThread currentThread]);
});
}
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"index==%ld", index);
});
}
2020-12-25 15:25:20.606309+0800 JeyRAC[81046:2174844] index==0
2020-12-25 15:25:20.606322+0800 JeyRAC[81046:2175021] index==1
2020-12-25 15:25:20.606373+0800 JeyRAC[81046:2175020] a-index==0----线程{number = 6, name = (null)}
2020-12-25 15:25:20.606470+0800 JeyRAC[81046:2174844] index==2
2020-12-25 15:25:20.606480+0800 JeyRAC[81046:2175021] index==3
2020-12-25 15:25:20.606504+0800 JeyRAC[81046:2175051] a-index==2----线程{number = 8, name = (null)}
2020-12-25 15:25:20.606463+0800 JeyRAC[81046:2175019] a-index==1----线程{number = 7, name = (null)}
2020-12-25 15:25:20.606574+0800 JeyRAC[81046:2174844] index==4
2020-12-25 15:25:20.606580+0800 JeyRAC[81046:2175021] index==5
2020-12-25 15:25:20.606549+0800 JeyRAC[81046:2175052] a-index==3----线程{number = 9, name = (null)}
2020-12-25 15:25:20.606573+0800 JeyRAC[81046:2175053] a-index==4----线程{number = 10, name = (null)}
2020-12-25 15:25:20.606651+0800 JeyRAC[81046:2174844] index==6
2020-12-25 15:25:20.606620+0800 JeyRAC[81046:2175020] a-index==5----线程{number = 6, name = (null)}
2020-12-25 15:25:20.606747+0800 JeyRAC[81046:2175022] index==7
2020-12-25 15:25:20.606752+0800 JeyRAC[81046:2175025] index==8
2020-12-25 15:25:20.606774+0800 JeyRAC[81046:2175023] index==9
2020-12-25 15:25:20.606991+0800 JeyRAC[81046:2175021] a-index==6----线程{number = 5, name = (null)}
2020-12-25 15:25:20.607094+0800 JeyRAC[81046:2175054] a-index==7----线程{number = 11, name = (null)}
2020-12-25 15:25:20.607109+0800 JeyRAC[81046:2175055] a-index==8----线程{number = 12, name = (null)}
2020-12-25 15:25:20.607153+0800 JeyRAC[81046:2175056] a-index==9----线程{number = 13, name = (null)}
两种for循环差不多,并行的异步中有的线程是相同的
GCD 栅栏方法:dispatch_barrier_async
在异步执行一些操作的时候,我们使用dispatch_barrier_async函数把异步操作暂时性的做成同步操作,就行一个栅栏一样分开
@property(nonatomic, strong) dispatch_queue_t queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
[self read];
[self read];
[self read];
[self write];
}
}
- (void)read {
dispatch_async(self.queue, ^{
sleep(1);
NSLog(@"read");
});
}
- (void)write
{
dispatch_barrier_async(self.queue, ^{
sleep(1);
NSLog(@"write");
});
}
输出
2018-12-03 16:42:26.268215+0800 001--Block[4828:304463] read
2018-12-03 16:42:26.268216+0800 001--Block[4828:304464] read
2018-12-03 16:42:26.268216+0800 001--Block[4828:304462] read
2018-12-03 16:42:26.268521+0800 001--Block[4828:304462] write
2018-12-03 16:42:26.268734+0800 001--Block[4828:304462] read
2018-12-03 16:42:26.268738+0800 001--Block[4828:304464] read
2018-12-03 16:42:26.268756+0800 001--Block[4828:304463] read
2018-12-03 16:42:26.269386+0800 001--Block[4828:304463] write
2018-12-03 16:42:26.269962+0800 001--Block[4828:304463] read
2018-12-03 16:42:26.269967+0800 001--Block[4828:304464] read
2018-12-03 16:42:26.269979+0800 001--Block[4828:304462] read
2018-12-03 16:42:26.271003+0800 001--Block[4828:304462] write
2018-12-03 16:42:26.271579+0800 001--Block[4828:304462] read
GCD 延时执行方法: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(), ^{
// 2.0秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
});
}
GCD 一次性代码(只执行一次):dispatch_once
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
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_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1
dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1
当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到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
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
NSLog(@"group---end");
});
}
输出:
2018-12-03 16:53:17.192618+0800 001--Block[4916:310729] currentThread---{number = 1, name = main}
2018-12-03 16:53:17.192842+0800 001--Block[4916:310729] group---begin
2018-12-03 16:53:19.198080+0800 001--Block[4916:310976] 1---{number = 4, name = (null)}
2018-12-03 16:53:19.198079+0800 001--Block[4916:310975] 2---{number = 3, name = (null)}
2018-12-03 16:53:21.203811+0800 001--Block[4916:310975] 2---{number = 3, name = (null)}
2018-12-03 16:53:21.203844+0800 001--Block[4916:310976] 1---{number = 4, name = (null)}
2018-12-03 16:53:23.205322+0800 001--Block[4916:310729] 3---{number = 1, name = main}
2018-12-03 16:53:25.206537+0800 001--Block[4916:310729] 3---{number = 1, name = main}
2018-12-03 16:53:25.206825+0800 001--Block[4916:310729] group---end
- (void)group{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t globalQ = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, globalQ, ^{
dispatch_async(globalQ, ^{
NSLog(@"1");
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_group_async(group, globalQ, ^{
dispatch_async(globalQ, ^{
NSLog(@"2");
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_group_async(group, globalQ, ^{
dispatch_async(globalQ, ^{
NSLog(@"3");
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, globalQ, ^{
NSLog(@"end");
});
}
没有dispatch_group_enter(group);dispatch_group_leave(group);,dispatch_group_async里面是个异步,NSLog(@"end");可能打印了,但是dispatch_group_async里面的异步可能还没执行,加上dispatch_group_enter(group);dispatch_group_leave(group);可解决
NSOperation介绍
NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象
NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行
好处:
1、可添加完成的代码块,在操作完成后执行
2、添加操作之间的依赖关系,方便的控制执行顺序
3、设定操作执行的优先级
4、可以很方便的取消一个操作的执行
5、使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled
既然是基于 GCD 的更高一层的封装。那么,GCD 中的一些概念同样适用于 NSOperation、NSOperationQueue。在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念
NSOperation常用属性和方法
1、开始取消操作
- (void)start:对于并发Operation需要重写该方法,也可以不把operation加入到队列中,手动触发执行,与调用普通方法一样
- (void)main:非并发Operation需要重写该方法
- (void)cancel:可取消操作,实质是标记 isCancelled 状态
2、判断操作状态方法
- (BOOL)isFinished; 判断操作是否已经结束
- (BOOL)isCancelled 判断操作是否已经标记为取消
- (BOOL)isExecuting;判断操作是否正在在运行
- (BOOL)isReady;判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
3、操作同步
- (void)waitUntilFinished;阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步
- (void)setCompletionBlock:(void (^)(void))block; 会在当前操作执行完毕时执行 completionBlock
- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成
- (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray*dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。
NSOperationQueue 常用属性和方法
-
1、取消/暂停/恢复操作
-
- (void)cancelAllOperations;
可以取消队列的所有操作 -
- (BOOL)isSuspended;
判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态 -
- (void)setSuspended:(BOOL)b;
可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列
-
-
2、操作同步
-
- (void)waitUntilAllOperationsAreFinished;
阻塞当前线程,直到队列中的操作全部执行完毕。
-
-
3、添加/获取操作
-
- (void)addOperationWithBlock:(void (^)(void))block;
向队列中添加一个 NSBlockOperation 类型操作对象 -
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束 -
- (NSArray *)operations;
当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除) -
- (NSUInteger)operationCount;
当前队列中的操作数
-
-
4、获取队列
-
+ (id)currentQueue;
获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。 -
+ (id)mainQueue;
获取主队列。
-
简单使用
NSOperation 需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行
实现步骤
1、创建操作:先将需要执行的操作封装到一个 NSOperation 对象中
2、创建队列:创建 NSOperationQueue 对象
3、将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中
NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作
1、使用子类 NSInvocationOperation
2、使用子类 NSBlockOperation
3、自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作。
使用子类 NSInvocationOperation
- (void)Operation1{
//1、创建NSInvocationOperation对象
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
//2、开始调用
[op start];
}
- (void)test{
for (NSInteger i = 0; i < 2; i++) {
NSLog(@"当前线程:%@",[NSThread currentThread]);
}
}
2018-12-03 17:09:59.378583+0800 001--Block[5018:320650] 当前线程:{number = 1, name = main}
2018-12-03 17:09:59.378855+0800 001--Block[5018:320650] 当前线程:{number = 1, name = main}
总结:在没有使用 NSOperationQueue、在主线程中单独使用使用子类 NSBlockOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。
将操作加入到队列中
NSOperation 需要配合 NSOperationQueue 来实现多线程,总共有两种方法:
1、- (void)addOperation:(NSOperation *)op; 需要先创建操作,再将创建好的操作加入到创建好的队列中去
2、- (void)addOperationWithBlock:(void (^)(void))block; 无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。
addOperation
- (void)Operation4{
//1、创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2、创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程1:%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程2:%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程3:%@",[NSThread currentThread]);
}];
//3、添加操作
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
2018-12-03 17:14:48.844950+0800 001--Block[5078:324522] 当前线程1:{number = 4, name = (null)}
2018-12-03 17:14:48.844951+0800 001--Block[5078:324525] 当前线程3:{number = 5, name = (null)}
2018-12-03 17:14:48.844950+0800 001--Block[5078:324523] 当前线程2:{number = 3, name = (null)}
addOperationWithBlock
无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。
- (void)Operation5{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
NSLog(@"当前线程1:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"当前线程2:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"当前线程3:%@",[NSThread currentThread]);
}];
}
这段代码和上面效果是一样的。
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount = 1; // 串行队列
queue.maxConcurrentOperationCount = 2; // 并发队列,一次只能执行两个并发队列
[queue addOperationWithBlock:^{
sleep(1);
NSLog(@"当前线程1:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
sleep(1);
NSLog(@"当前线程2:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"当前线程3:%@",[NSThread currentThread]);
}];
设置最大并发量,1,2执行完了才会到3
NSOperation 操作依赖
NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序
- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray*dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组
- (void)Operation6{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2、创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"当前线程1:%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"当前线程2:%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程3:%@",[NSThread currentThread]);
}];
//3、添加依赖
[op1 addDependency:op2];
[op1 addDependency:op3];
//4、添加操作
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
2018-12-03 17:22:02.254933+0800 001--Block[5286:333230] 当前线程3:{number = 3, name = (null)}
2018-12-03 17:22:04.259007+0800 001--Block[5286:333238] 当前线程2:{number = 4, name = (null)}
2018-12-03 17:22:05.264331+0800 001--Block[5286:333230] 当前线程1:{number = 3, name = (null)}