iOS 多线程-基础篇

学习线程之前我们先温习下

进程:
屏幕快照 2018-12-01 下午2.52.13.png

线程:
屏幕快照 2018-12-01 下午2.54.40.png

多线程的原理:同一时间,cpu只能处理一条线程,只有一条线程在工作,多线程并发执行,其实是cpu快速的在多条线程间切换。
优点:1、提高程序的执行效率
2、提高cpu的利用率
缺点: 线程多了就会消耗大量的cpu资源,降低程序性能,线程调度频率也会降低,同时耗电。

多线程.png

一、 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");

看一些案例:

  1. 同步并发嵌套异步
- (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先打印

  1. //同步并发嵌套同步
- (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

同步任务会阻塞当前线程一步一步往下走就对了

  1. 同步串行嵌套异步步
- (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前打印,异步跟这里就没什么关系了

  1. 主队列中异步:不会开辟新线程,不会阻塞当前线程
    主队列中添加的任务需要等主线中的任务执行,再执行
- (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)}

你可能感兴趣的:(iOS 多线程-基础篇)