面试宝点

(转)iOS中的多线程方案

2021-04-13  本文已影响0人  大成小栈

每一个获得CPU的任务只能运行一个时间片规定的时间,时间片是现代的CPU很重要的一个特性。

其实线程对操作系统来说就是一段代码以及运行时数据,操作系统会为每个线程保存相关的数据(线程运行的上下文环境)。当接收到来自CPU的时间片中断事件时,就会按一定规则从这些线程中选择一个,恢复它的运行时数据,这样CPU就可以继续执行这个线程了。

就单核CUP而言,并没法实现真正的并发执行,只是CPU快速地在多线程间调度,CPU调度线程足够快造成了并发执行的假象。并且就单核CPU而言多线程可以解决线程阻塞的问题,但是其本身运行效率并没有提高,多CPU的并行运算才真正解决了运行效率问题。

系统中正在运行的每一个应用程序都是一个进程,每个进程系统都会分配给它独立的内存运行。也就是说,在iOS系统中中,每一个应用都是一个进程。

一个进程的所有任务都在线程中进行,因此每个进程至少要有一个线程,也就是主线程。那多线程其实就是一个进程开启多条线程,让所有任务并发执行。

多线程在实现了进程内的资源共享、效率的提升。同时,线程间在一定程度上相对独立,它是程序执行流的最小调度单元,是进程中的一个实体,有自己栈和寄存器。

1. Pthreads && NSThread

先来看与线程有最直接关系的一套C的API:

Pthreads

POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。

Pthreads的基本用法,创建一个线程去执行一个任务:

// 记得引入头文件#import "pthread.h"
-(void)pthreadsDoTask{
    /*
     pthread_t:线程指针
     pthread_attr_t:线程属性
     pthread_mutex_t:互斥对象
     pthread_mutexattr_t:互斥属性对象
     pthread_cond_t:条件变量
     pthread_condattr_t:条件属性对象
     pthread_key_t:线程数据键
     pthread_rwlock_t:读写锁
     //
     pthread_create():创建一个线程
     pthread_exit():终止当前线程
     pthread_cancel():中断另外一个线程的运行
     pthread_join():阻塞当前的线程,直到另外一个线程运行结束
     pthread_attr_init():初始化线程的属性
     pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
     pthread_attr_getdetachstate():获取脱离状态的属性
     pthread_attr_destroy():删除线程的属性
     pthread_kill():向线程发送一个信号
     pthread_equal(): 对两个线程的线程标识号进行比较
     pthread_detach(): 分离线程
     pthread_self(): 查询线程自身线程标识号
     //
     *创建线程
     int pthread_create(pthread_t _Nullable * _Nonnull __restrict, //指向新建线程标识符的指针
     const pthread_attr_t * _Nullable __restrict,  //设置线程属性。默认值NULL。
     void * _Nullable (* _Nonnull)(void * _Nullable),  //该线程运行函数的地址
     void * _Nullable __restrict);  //运行函数所需的参数
     *返回值:
     *若线程创建成功,则返回0
     *若线程创建失败,则返回出错编号
     */

    //
    pthread_t thread = NULL;
    NSString *params = @"Hello World";
    int result = pthread_create(&thread, NULL, threadTask, (__bridge void *)(params));
    result == 0 ? NSLog(@"creat thread success") : NSLog(@"creat thread failure");
    //设置子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源
    pthread_detach(thread);
}
void *threadTask(void *params) {
    NSLog(@"%@ - %@", [NSThread currentThread], (__bridge NSString *)(params));
    return NULL;
}

输出结果:

ThreadDemo[1197:143578] creat thread success
ThreadDemo[1197:143649] {number = 3, name = (null)} - Hello World

该任务是在新开辟的线程中执行的,但是很多东西需要自己管理,单单是任务队列以及线程生命周期的管理就够麻烦,很少使用。

NSThread

NSThread面向对象,比一下Pthreads简单明了,看一下系统提供给我们的API自然就知道怎么用:

@interface NSThread : NSObject
//当前线程
@property (class, readonly, strong) NSThread *currentThread;
//使用类方法创建线程执行任务
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
//判断当前是否为多线程
+ (BOOL)isMultiThreaded;
//指定线程的线程参数,例如设置当前线程的断言处理器。
@property (readonly, retain) NSMutableDictionary *threadDictionary;
//当前线程暂停到某个时间
+ (void)sleepUntilDate:(NSDate *)date;
//当前线程暂停一段时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//退出当前线程
+ (void)exit;
//当前线程优先级
+ (double)threadPriority;
//设置当前线程优先级
+ (BOOL)setThreadPriority:(double)p;
//指定线程对象优先级 0.0~1.0,默认值为0.5
@property double threadPriority NS_AVAILABLE(10_6, 4_0);
//服务质量
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
//线程名称
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
//栈区大小
@property NSUInteger stackSize NS_AVAILABLE(10_5, 2_0);
//是否为主线程
@property (class, readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
//获取主线程
@property (class, readonly, strong) NSThread *mainThread NS_AVAILABLE(10_5, 2_0);
//初始化
- (instancetype)init NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER;
//实例方法初始化,需要再调用start方法
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
//线程状态,正在执行
@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);
//线程状态,正在完成
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);
//线程状态,已经取消
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);
//取消,仅仅改变线程状态,并不能像exist一样真正的终止线程
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//开始
- (void)start NS_AVAILABLE(10_5, 2_0);
//线程需要执行的代码,一般写子类的时候会用到
- (void)main NS_AVAILABLE(10_5, 2_0);
@end
另外,还有一个NSObject的分类,瞅一眼:
@interface NSObject (NSThreadPerformAdditions)
//隐式的创建并启动线程,并在指定的线程(主线程或子线程)上执行方法。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
@end

正常情况下,在主线程中直接下载一张图片,会出现线程阻塞的情况。多线程的使用能解决线程阻塞的问题,例:

-(void)creatBigImageView{
    self.bigImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:_bigImageView];
    UIButton *startButton = [UIButton buttonWithType:UIButtonTypeSystem];
    startButton.frame = CGRectMake(0, 20, self.view.frame.size.width / 2, 50);
    startButton.backgroundColor = [UIColor grayColor];
    [startButton setTitle:@"开始加载" forState:UIControlStateNormal];
    [startButton addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startButton];

    UIButton *jamButton = [UIButton buttonWithType:UIButtonTypeSystem];
    jamButton.frame = CGRectMake(self.view.frame.size.width / 2, 20, self.view.frame.size.width / 2, 50);
    jamButton.backgroundColor = [UIColor grayColor];
    [jamButton setTitle:@"阻塞测试" forState:UIControlStateNormal];
    [jamButton addTarget:self action:@selector(jamTest) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:jamButton];
}
-(void)jamTest{
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"阻塞测试" message:@"" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
    [alertView show];
}
-(void)loadImageWithMultiThread{
    //方法1:使用对象方法
    //NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
    //??启动一个线程并非就一定立即执行,而是处于就绪状态,当CUP调度时才真正执行
    //[thread start];

    //方法2:使用类方法
    [NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}
-(void)loadImage{
    NSURL *imageUrl = [NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    //必须在主线程更新UI,Object:代表调用方法的参数,不过只能传递一个参数(如果有多个参数请使用对象进行封装),waitUntilDone:是否线程任务完成执行
    [self performSelectorOnMainThread:@selector(updateImageData:) withObject:imageData waitUntilDone:YES];

    //[self updateImageData:imageData];
}
-(void)updateImageData:(NSData*)imageData{
    UIImage *image = [UIImage imageWithData:imageData];
    self.bigImageView.image = image;
}
先出现弹窗,异步示图片

NSThread虽然比Pthreads好用,但假如我有很多不同类型的任务,每个任务之间还有联系和依赖,NSThread处理起来又会变的比较复杂了,工作中用到最多的就是[NSThread currentThread]了。

2. GCD

GCD(Grand Central Dispatch)是基于C语言的一套多线程开发API,苹果官方推荐的多线程开发方式。它能自动管理线程的生命周期、任务(任务就是要执行的那段代码)的执行规则。

2.1 任务管理方式—队列

上面说当我们要管理多个任务时,线程开发给我们带来了一定的技术难度,或者说不方便性,GCD给出了我们统一管理任务的方式,那就是队列。我们来看一下iOS多线程操作中的队列:(不管是串行还是并行,队列都是按照FIFO的原则依次触发任务)

两个通用队列:

两个特殊队列:

任务执行方式

任务除了管理,还得执行,在GCD中并不能直接开辟线程执行任务,所以在任务加入队列之后,GCD给出了两种执行方式——同步执行(sync)和异步执行(async)。

下面看一个线程死锁的问题:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

运行结果:

打印结果:

ThreadDemo[5615:874679] 1========{number = 1, name = main}

造成死锁的原因:
我们先做一个定义:- (void)viewDidLoad{} ---> (任务A(GCD同步函数 --->任务B))。首先,任务A在主队列,并且已经开始执行,在主线程打印出1===... ...,然后这时任务B被加入到主队列中,并且同步执行(系统同步执行,不开新的线程)。即,造成了B等待A完成,A在当前位置完成不了的局面,死锁了。

如下这样,是不死锁的:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    NSLog(@"2========%@",[NSThread currentThread]);
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[5803:939324] 1========{number = 1, name = main}
ThreadDemo[5803:939324] 2========{number = 1, name = main}
ThreadDemo[5803:939324] 3========{number = 1, name = main}

注意:顺序打印,没毛病,全在主线程执行,而且顺序执行,那它们一定是在主队列同步执行的啊!那为什么没有死锁?苹果的操作系统果然高深啊!

其实这里有一个误区,那就是任务在主线程顺序执行就是主队列。其实一点关系都没有,如果当前在主线程,同步执行任务,不管在什么队列任务都是顺序执行。把所有任务都以异步执行的方式加入到主队列中,你会发现它们也是顺序执行的。而且任务的执行不一定非得撕扯白咧的加入到队列中才可以啊!

相信你知道上面的死锁情况后,你一定会手贱改成这样试试:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[5830:947858] 1========{number = 1, name = main}
ThreadDemo[5830:947858] 2========{number = 1, name = main}
ThreadDemo[5830:947858] 3========{number = 1, name = main}

你发现正常执行了,并且是顺序执行的,你是不是若有所思,没错,你想的和我想的是一样的,和上述情况一样,任务A在主队列中,但是任务B加入到了全局队列,这时候,任务A和任务B没有队列的约束,所以任务B就先执行喽,执行完毕之后函数返回,任务A接着执行。

我猜你一定手贱这么改过:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[5911:962470] 1========{number = 1, name = main}
ThreadDemo[5911:962470] 3========{number = 1, name = main}
ThreadDemo[5911:962470] 2========{number = 1, name = main}

结果顺序打印了,而且也不会死锁,明明都是加到主队列里了啊,其实当任务A在执行时,任务B加入到了主队列,注意哦,是异步执行,所以dispatch函数不会等到Block执行完成才返回,dispatch函数返回后,那任务A可以继续执行,Block任务我们可以认为在下一帧顺序加入队列,并且默认无限下一帧执行。这就是为什么你看到2===... ...是最后输出的了。(??一个函数的有多个内部函数异步执行时,不会造成死锁的同时,任务A执行完毕后,这些异步执行的内部函数会顺序执行)。

2.2 队列与执行方式组合

上面说了系统自带的两个队列,下面我们来用自己创建的队列研究一下各种搭配情况。
我们先创建两个队列,并且测试方法都是在主线程中调用:

//串行队列
self.serialQueue = dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL);
//并行队列
self.concurrentQueue = dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT);

1. 串行队列 + 同步执行

-(void)queue_taskTest{
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[6735:1064390] 1========{number = 1, name = main}
ThreadDemo[6735:1064390] 2========{number = 1, name = main}
ThreadDemo[6735:1064390] 3========{number = 1, name = main}
ThreadDemo[6735:1064390] 4========{number = 1, name = main}

全部都在当前线程顺序执行,也就是说,同步执行不具备开辟新线程的能力。

2. 串行队列 + 异步执行

-(void)queue_taskTest{
    dispatch_async(self.serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[6774:1073235] 4========{number = 1, name = main}
ThreadDemo[6774:1073290] 1========{number = 3, name = (null)}
ThreadDemo[6774:1073290] 2========{number = 3, name = (null)}
ThreadDemo[6774:1073290] 3========{number = 3, name = (null)}

先打印了4,然后顺序在子线程中打印1,2,3。说明异步执行具有开辟新线程的能力,并且串行队列必须等到前一个任务执行完才能开始执行下一个任务,同时,异步执行会使内部函数率先返回,不会与正在执行的外部函数发生死锁。

3. 并行队列 + 同步执行

-(void)queue_taskTest{
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

运行结果:

ThreadDemo[7012:1113594] 1========{number = 1, name = main}
ThreadDemo[7012:1113594] 2========{number = 1, name = main}
ThreadDemo[7012:1113594] 3========{number = 1, name = main}
ThreadDemo[7012:1113594] 4========{number = 1, name = main}

未开启新的线程执行任务,并且Block函数执行完成后dispatch函数才会返回,才能继续向下执行,所以我们看到的结果是顺序打印的。

4. 并行队列 + 异步执行

-(void)queue_taskTest{
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[7042:1117492] 1========{number = 3, name = (null)}
ThreadDemo[7042:1117491] 3========{number = 5, name = (null)}
ThreadDemo[7042:1117451] 4========{number = 1, name = main}
ThreadDemo[7042:1117494] 2========{number = 4, name = (null)}

开辟了多个线程,触发任务的时机是顺序的,但是我们看到完成任务的时间却是随机的,这取决于CPU对于不同线程的调度分配,但是,线程不是无条件无限开辟的,当任务量足够大时,线程是会重复利用的。

划一下重点:

  1. 对于单核CPU来说,不存在真正意义上的并行,同时对于非等待任务,多线程并没有真正意义提高效率。
  2. 线程可以简单的认为就是一段代码+运行时数据。
  3. 同步执行会在当前线程执行任务,不具备开辟线程的能力或者说没有必要开辟新的线程。并且,同步执行必须等到Block函数执行完毕,dispatch函数才会返回,从而阻塞同一串行队列中外部方法的执行。
  4. 异步执行dispatch函数会直接返回,Block函数我们可以认为它会在下一帧加入队列,并根据所在队列目前的任务情况无限下一帧执行,从而不会阻塞当前外部任务的执行。同时,只有异步执行才有开辟新线程的必要,但是异步执行不一定会开辟新线程。
  5. 只要是队列,肯定是FIFO(先进先出),但是谁先执行完要看第1条。
  6. 只要是串行队列,肯定要等上一个任务执行完成,才能开始下一个任务。但是并行队列当上一个任务开始执行后,下一个任务就可以开始执行。
  7. 想要开辟新线程必须让任务在异步执行,想要开辟多个线程,只有让任务在并行队列中异步执行才可以。执行方式和队列类型多层组合在一定程度上能够实现对于代码执行顺序的调度。
  8. 同步+串行:未开辟新线程,串行执行任务;同步+并行:未开辟新线程,串行执行任务;异步+串行:新开辟一条线程,串行执行任务;异步+并行:开辟多条新线程,并行执行任务;在主线程中同步使用主队列执行任务,会造成死锁。
  9. 对于多核CPU来说,线程数量也不能无限开辟,线程的开辟同样会消耗资源,过多线程同时处理任务并不是你想像中的人多力量大。

2.3 GCD其他函数用法

dispatch_after

用于任务延时执行,如果队列为主队列,那么任务在主线程执行,如果队列为全局队列或者自己创建的队列,那么任务在子线程执行。代码如下:

-(void)GCDDelay{
    //主队列延时
    dispatch_time_t when_main = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(when_main, dispatch_get_main_queue(), ^{
        NSLog(@"main_%@",[NSThread currentThread]);
    });
    //全局队列延时
    dispatch_time_t when_global = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));
    dispatch_after(when_global, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"global_%@",[NSThread currentThread]);
    });
    //自定义队列延时
    dispatch_time_t when_custom = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    dispatch_after(when_custom, self.serialQueue, ^{
        NSLog(@"custom_%@",[NSThread currentThread]);
    });
}

打印结果:

ThreadDemo[1508:499647] main_{number = 1, name = main}
ThreadDemo[1508:499697] global_{number = 3, name = (null)}
ThreadDemo[1508:499697] custom_{number = 3, name = (null)}

dispatch_once

保证函数在整个生命周期内只会执行一次,看代码。

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

打印结果:

ThreadDemo[1524:509261] {number = 1, name = main}

无论你怎么疯狂的点击,在第一次打印之后,输出台便岿然不动。

dispatch_group_async & dispatch_group_notify

试想,现在你要下载两张小图,并且你要等两张图都下载完成之后把他们拼起来,你要怎么做?

其实方法有很多,比如你可以一张一张下载,再比如使用局部变量和Block实现计数,但是既然今天我们讲到这,那我们就得入乡随俗,用GCD来实现,有一个神器的东西叫做队列组,当加入到队列组中的所有任务执行完成之后,会调用dispatch_group_notify函数通知任务全部完成,代码如下:

-(void)GCDGroup{
    //
    [self jointImageView];
    //
    dispatch_group_t group = dispatch_group_create();
    __block UIImage *image_1 = nil;
    __block UIImage *image_2 = nil;
    //在group中添加一个任务
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image_1 = [self imageWithPath:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502706256731&di=371f5fd17184944d7e2b594142cd7061&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201605%2F14%2F20160514165210_LRCji.jpeg"];

    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image_2 = [self imageWithPath:@"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=776127947,2002573948&fm=26&gp=0.jpg"];
    });
    //group中所有任务执行完毕,通知该方法执行
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        self.imageView_1.image = image_1;
        self.imageView_2.image = image_2;
        //
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0f);
        [image_2 drawInRect:CGRectMake(0, 0, 100, 100)];
        [image_1 drawInRect:CGRectMake(100, 0, 100, 100)];
        UIImage *image_3 = UIGraphicsGetImageFromCurrentImageContext();
        self.imageView_3.image = image_3;
        UIGraphicsEndImageContext();
    });
}
-(void)jointImageView{
    self.imageView_1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 50, 100, 100)];
    [self.view addSubview:_imageView_1];

    self.imageView_2 = [[UIImageView alloc] initWithFrame:CGRectMake(140, 50, 100, 100)];
    [self.view addSubview:_imageView_2];

    self.imageView_3 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 200, 200, 100)];
    [self.view addSubview:_imageView_3];

    self.imageView_1.layer.borderColor = self.imageView_2.layer.borderColor = self.imageView_3.layer.borderColor = [UIColor grayColor].CGColor;
    self.imageView_1.layer.borderWidth = self.imageView_2.layer.borderWidth = self.imageView_3.layer.borderWidth = 1;
}

4. dispatch_barrier_async

使用此方法创建的任务,会查找当前队列中有没有其他任务要执行,如果有,则等待已有任务执行完毕后再执行,同时,在此任务之后进入队列的任务,需要等待此任务执行完成后,才能执行。看代码,老铁。

-(void)GCDbarrier{

    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });

//    dispatch_barrier_async(self.concurrentQueue, ^{
//        NSLog(@"任务barrier");
//    });

//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

运行结果:

ThreadDemo[1816:673351] 任务3
ThreadDemo[1816:673353] 任务1
ThreadDemo[1816:673350] 任务2
ThreadDemo[1816:673370] 任务4

是不是如你所料,牛逼大了,下面我们打开第一句注释:

-(void)GCDbarrier{

    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });

    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任务barrier");
    });

//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

打印结果:

ThreadDemo[1833:678739] 任务2
ThreadDemo[1833:678740] 任务1
ThreadDemo[1833:678740] 任务barrier
ThreadDemo[1833:678740] 任务3
ThreadDemo[1833:678739] 任务4

这个结果和我们上面的解释完美契合,我们可以简单的控制函数执行的顺序了,你离大牛又近了一步,如果现在的你不会怀疑还有dispatch_barrier_sync这个函数的话,说明... ...嘿嘿嘿,我们看一下这个函数和上面我们用到的函数的区别,你一定想到了,再打开第二个和第三个注释,如下:

-(void)GCDbarrier{        
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });

    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任务barrier");
    });

    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

运行结果:

ThreadDemo[1853:692434] 任务1
ThreadDemo[1853:692421] 任务2
ThreadDemo[1853:692387] big
ThreadDemo[1853:692421] 任务barrier
ThreadDemo[1853:692387] apple
ThreadDemo[1853:692421] 任务3
ThreadDemo[1853:692434] 任务4

不要着急,我们换一下函数:

-(void)GCDbarrier{

    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });

    dispatch_barrier_sync(self.concurrentQueue, ^{
        NSLog(@"任务barrier");
    });

    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

打印结果:

ThreadDemo[1874:711841] 任务1
ThreadDemo[1874:711828] 任务2
ThreadDemo[1874:711793] 任务barrier
ThreadDemo[1874:711793] big
ThreadDemo[1874:711793] apple
ThreadDemo[1874:711828] 任务3
ThreadDemo[1874:711841] 任务4

老铁,发现了吗?这两个函数对于队列的栅栏作用是一样的,但是对于该函数相对于其他内部函数遵循了最开始说到的同步和异步的规则。你是不是有点懵逼,如果你蒙蔽了,那么请在每一个输出后面打印出当前的线程,如果你还是懵逼,那么请你重新看,有劳,不谢!

5. dispatch_apply

该函数用于重复执行某个任务,如果任务队列是并行队列,重复执行的任务会并发执行,如果任务队列为串行队列,则任务会顺序执行,需要注意的是,该函数为同步函数,要防止线程阻塞和死锁哦,老铁。

串行队列:

-(void)GCDApply{
    //重复执行
    dispatch_apply(5, self.serialQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

运行结果:

ThreadDemo[1446:158101] 第0次_{number = 1, name = main}
ThreadDemo[1446:158101] 第1次_{number = 1, name = main}
ThreadDemo[1446:158101] 第2次_{number = 1, name = main}
ThreadDemo[1446:158101] 第3次_{number = 1, name = main}
ThreadDemo[1446:158101] 第4次_{number = 1, name = main}

并行队列:

-(void)GCDApply{
    //重复执行
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

运行结果:

ThreadDemo[1461:160567] 第2次_{number = 4, name = (null)}
ThreadDemo[1461:160534] 第0次_{number = 1, name = main}
ThreadDemo[1461:160566] 第3次_{number = 5, name = (null)}
ThreadDemo[1461:160569] 第1次_{number = 3, name = (null)}
ThreadDemo[1461:160567] 第4次_{number = 4, name = (null)}

死锁:

-(void)GCDApply{
    //重复执行
    dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

运行结果:

image

6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

看这几个函数的时候你需要抛开队列,丢掉同步异步,不要把它们想到一起,混为一谈,信号量只是控制任务执行的一个条件而已,相对于上面通过队列以及执行方式来控制线程的开辟和任务的执行,它更贴近对于任务直接的控制。类似于单个队列的最大并发数的控制机制,提高并行效率的同时,也防止太多线程的开辟对CPU早层负面的效率负担。

dispatch_semaphore_create创建信号量,初始值不能小于0;

dispatch_semaphore_wait等待降低信号量,也就是信号量-1;

dispatch_semaphore_signal提高信号量,也就是信号量+1;

dispatch_semaphore_wait和dispatch_semaphore_signal通常配对使用。

看一下代码吧,老铁。

-(void)GCDSemaphore{
    //
    //dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        //dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            //dispatch_semaphore_signal(semaphore);
        });
    });
}

你能猜到运行结果吗?没错,就是你想的这样,开辟了5个线程执行任务。

ThreadDemo[1970:506692] 第0次_{number = 3, name = (null)}
ThreadDemo[1970:506711] 第1次_{number = 4, name = (null)}
ThreadDemo[1970:506713] 第2次_{number = 5, name = (null)}
ThreadDemo[1970:506691] 第3次_{number = 6, name = (null)}
ThreadDemo[1970:506694] 第4次_{number = 7, name = (null)}

下一步你一定猜到了,把注释的代码打开:

-(void)GCDSemaphore{
    //
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
    });
}

运行结果:

ThreadDemo[2020:513651] 第0次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第1次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第2次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第3次_{number = 3, name = (null)}
ThreadDemo[2020:513651] 第4次_{number = 3, name = (null)}

很明显,我开始说的是对的,哈哈哈哈,信号量是控制任务执行的重要条件,当信号量为0时,所有任务等待,信号量越大,允许可并行执行的任务数量越多。

实践出真知,还是要掌握用法和习惯,看的再多还是觉得虚。

3. NSOperation && NSOperationQueue

NSOperation、NSOperationQueue是苹果对于GCD的封装,其中,NSOperation就是上面所说的任务,但是这个类不能直接使用,我们要用他的两个子类,NSBlockOperation和NSInvocationOperation。而NSOperationQueue就类似于GCD中的队列,用于管理加入到其中的任务。

NSOperation

它提供了关于任务的执行,取消,以及随时获取任务的状态,添加任务依赖以及优先级等方法和属性,相对于GCD提供的方法来说,更直观,更方便,并且提供了更多的控制接口。几个方法和属性先了解一下:

@interface NSOperation : NSObject {
@private
    id _private;
    int32_t _private1;
#if __LP64__
    int32_t _private1b;
#endif
}
- (void)start;//启动任务 默认加入到当前队列
- (void)main;//自定义NSOperation,写一个子类,重写这个方法,在这个方法里面添加需要执行的操作。
@property (readonly, getter=isCancelled) BOOL cancelled;//是否已经取消,只读
- (void)cancel;//取消任务
@property (readonly, getter=isExecuting) BOOL executing;//正在执行,只读
@property (readonly, getter=isFinished) BOOL finished;//执行结束,只读
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);//是否并发,只读
@property (readonly, getter=isReady) BOOL ready;//准备执行
- (void)addDependency:(NSOperation *)op;//添加依赖
- (void)removeDependency:(NSOperation *)op;//移除依赖
@property (readonly, copy) NSArray *dependencies;//所有依赖关系,只读
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};//系统提供的优先级关系枚举
@property NSOperationQueuePriority queuePriority;//执行优先级
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);//任务执行完成之后的回调
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);//阻塞当前线程,等到某个operation执行完毕。
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);//已废弃,用qualityOfService替代。
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);//任务名称
@end

然而,NSOperation本身是个抽象类,不能直接使用,我们有三种方式赋予它新的生命,就是下面这三个东西,您坐稳看好。

NSOperation自定义子类

这是我要说的第一个任务类型,我们可以自定义继承于NSOperation的子类,并重写父类提供的方法,实现一波具有特殊意义的任务。比如我们去下载一个图片:

//// .h
#import "NSOperation.h"

@protocol YSImageDownLoadOperationDelegate 
-(void)YSImageDownLoadFinished:(UIImage*)image;
@end

@interface YSImageDownLoadOperation : NSOperation
-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id)delegate;
@end

//// .m
#import "YSImageDownLoadOperation.h"

@implementation YSImageDownLoadOperation{
    NSURL *_imageUrl;
    id _delegate;
}
-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id)delegate{
    if (self == [super init]) {
        _imageUrl = imageUrl;
        _delegate = delegate;
    }
    return self;
}
-(void)main{
    @autoreleasepool {
        UIImage *image = [self imageWithUrl:_imageUrl];
        if (_delegate && [_delegate respondsToSelector:@selector(YSImageDownLoadFinished:)]) {
            [_delegate YSImageDownLoadFinished:image];
        }
    }
}
-(UIImage*)imageWithUrl:(NSURL*)url{
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:imageData];
    return image;
}
@end
然后调用:
-(void)YSDownLoadImageOperationRun{
    YSImageDownLoadOperation *ysOper = [[YSImageDownLoadOperation alloc] initOperationWithUrl:[NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"] delegate:self];
    [ysOper start];
}
-(void)YSImageDownLoadFinished:(UIImage *)image{
    NSLog(@"%@",image);
}

运行打印结果:

ThreadDemo[4141:1100329] , {700, 1050}

自定义的任务更具有指向性,可满足特定的需求,但是一般用的比较少。

NSBlockOperation

第二个,就是系统提供的NSOperation的子类NSBlockOperation,我们看一下他提供的API:

@interface NSBlockOperation : NSOperation {
@private
    id _private2;
    void *_reserved2;
}
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray *executionBlocks;
@end

很简单,就这几个,我们就用它实现一个任务:

-(void)NSBlockOperationRun{
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationRun_%@_%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
    }];
    [blockOper start];
}

运行结果:

ThreadDemo[4313:1121900] NSBlockOperationRun_{name = 'NSOperationQueue Main Queue'}_{number = 1, name = main}

我们发现这个任务是在当前线程顺序执行的,我们发现还有一个方法addExecutionBlock:试一下:

-(void)NSBlockOperationRun{
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationRun_1_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{
        NSLog(@"NSBlockOperationRun_2_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{
        NSLog(@"NSBlockOperationRun_3_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{
        NSLog(@"NSBlockOperationRun_4_%@",[NSThread currentThread]);
    }];
    [blockOper start];
}

打印结果:

ThreadDemo[4516:1169835] NSBlockOperationRun_1_{number = 1, name = main}
ThreadDemo[4516:1169875] NSBlockOperationRun_3_{number = 4, name = (null)}
ThreadDemo[4516:1169877] NSBlockOperationRun_4_{number = 5, name = (null)}
ThreadDemo[4516:1169893] NSBlockOperationRun_2_{number = 3, name = (null)}

从打印结果来看,这个4个任务是异步并发执行的,开辟了多条线程。

NSInvocationOperation

第三个,就是它了,同样也是系统提供给我们的一个任务类,基于一个target对象以及一个selector来创建任务,具体代码:

-(void)NSInvocationOperationRun{
    NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
    [invocationOper start];
}
-(void)invocationOperSel{
    NSLog(@"NSInvocationOperationRun_%@",[NSThread currentThread]);
}

运行结果:

ThreadDemo[4538:1173118] NSInvocationOperationRun_{number = 1, name = main}

运行结果与NSBlockOperation单个block函数的执行方式相同,同步顺序执行。的确系统的封装给予我们关于任务更直观的东西,但是对于多个任务的控制机制并不完善,所以我们有请下一位,也许你会眼前一亮。

NSOperationQueue

上面说道我们创建的NSOperation任务对象可以通过start方法来执行,同样我们可以把这个任务对象添加到一个NSOperationQueue对象中去执行。其API如下:

@interface NSOperationQueue : NSObject {
@private
    id _private;
    void *_reserved;
}
- (void)addOperation:(NSOperation *)op;//添加任务
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);//添加一组任务
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);//添加一个block形式的任务
@property (readonly, copy) NSArray *operations;//队列中所有的任务数组
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);//队列中的任务数
@property NSInteger maxConcurrentOperationCount;//最大并发数
@property (getter=isSuspended) BOOL suspended;//暂停
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);//名称
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
- (void)cancelAllOperations;//取消队列中的所有任务
- (void)waitUntilAllOperationsAreFinished;//阻塞当前线程,等到队列中的任务全部执行完毕。
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);//获取当前队列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);//获取主队列
#endif
@end

来一段代码开心开心:

-(void)NSOperationQueueRun{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
    [queue addOperation:invocationOper];
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationRun_%@",[NSThread currentThread]);
    }];
    [queue addOperation:blockOper];
    [queue addOperationWithBlock:^{
        NSLog(@"QUEUEBlockOperationRun_%@",[NSThread currentThread]);
    }];
}

打印结果:

ThreadDemo[4761:1205689] NSBlockOperationRun_{number = 4, name = (null)}
ThreadDemo[4761:1205691] NSInvocationOperationRun_{number = 3, name = (null)}
ThreadDemo[4761:1205706] QUEUEBlockOperationRun_{number = 5, name = (null)}

我们发现,加入队列之后不用调用任务的start方法,队列会帮你管理任务的执行情况。上述执行结果说明,这些任务在队列中为并发执行的。

下面我们改变一下任务的优先级:

invocationOper.queuePriority = NSOperationQueuePriorityVeryLow;

运行结果:

ThreadDemo[4894:1218440] QUEUEBlockOperationRun_{number = 3, name = (null)}
ThreadDemo[4894:1218442] NSBlockOperationRun_{number = 4, name = (null)}
ThreadDemo[4894:1218457] NSInvocationOperationRun_{number = 5, name = (null)}

我们发现优先级低的任务会后执行,但是,这并不是绝对的,还有很多东西可以左右CPU分配,以及操作系统对于任务和线程的控制,只能说,优先级会在一定程度上让优先级高的任务开始执行。同时,优先级只对同一队列中的任务有效哦。下面我们就看一个会忽视优先级的情况。

添加依赖关系

-(void)NSOperationQueueRun{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blockOper_1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 1000; i++) {
            NSLog(@"blockOper_1_%@_%@",@(i),[NSThread currentThread]);
        }
    }];

    NSBlockOperation *blockOper_2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 1000; i++) {
            NSLog(@"blockOper_2_%@_%@",@(i),[NSThread currentThread]);
        }
    }];

    [blockOper_1 addDependency:blockOper_2];
    [queue addOperation:blockOper_1];
    [queue addOperation:blockOper_2];
}

打印结果:

ThreadDemo[5066:1233824] blockOper_2_0_{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_1_{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_2_{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_3_{number = 3, name = (null)}
... ...
ThreadDemo[5066:1233824] blockOper_2_999_{number = 3, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_0_{number = 4, name = (null)}
... ...
ThreadDemo[5066:1233822] blockOper_1_997_{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_998_{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_999_{number = 4, name = (null)}

通过打印结果我们可以看到,添加依赖之后,依赖任务必须等待被依赖任务执行完毕之后才会开始执行。(??,就算依赖任务的优先级再高,也是被依赖任务先执行,同时,和优先级不同,依赖关系不受队列的局限,爱哪哪,只要是我依赖于你,那你必须先执行完,我才执行。)

队列的最大并发数

就是说,这个队列最多可以有多少任务同时执行,或者说最多开辟多少条线程,如果设置为1,那就一次只能执行一个任务,但是,不要以为这和GCD的串行队列一样,就算最大并发数为1,队列任务的执行顺序依然取决于很多因素。

关于NSOperationQueue还有取消啊,暂停啊等操作方式,大家可以试一下,应该注意的是,和学习GCD的方式不同,不要总是站在面向过程的角度看带这些面向对象的类,因为它的面相对象化的封装过程中,肯定有很多你看不到的面相过程的操作,所以你也没有必要用使用GCD的思想来套用它,否则你可能会迷糊的一塌糊涂。

4. 线程锁

下面说一下线程锁机制,多线程操作是多个线程并行的,所以同一块资源可能在同一时间被多个线程访问。以买车票举例,我们先实现一段代码:

- (void)viewDidLoad {
        [super viewDidLoad];
        self.sourceArray_m = [NSMutableArray new];
        [_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
        [self threadLock];
    }
    -(void)threadLock{
        for (int i = 0; i < 8; i++) {
            dispatch_async(self.concurrentQueue, ^{
                NSLog(@"%@",[self sourceOut]) ;
            });
        }
    }
    -(NSString*)sourceOut{
        NSString *source = @"没有了,取光了";
        if (_sourceArray_m.count > 0) {
            source = [_sourceArray_m lastObject];
            [_sourceArray_m removeLastObject];
        }
        return source;
}

运行打印结果:

ThreadDemo[5540:1291666] 6
ThreadDemo[5540:1291669] 6
ThreadDemo[5540:1291682] 5
ThreadDemo[5540:1291667] 4
ThreadDemo[5540:1291683] 3
ThreadDemo[5540:1291666] 2
ThreadDemo[5540:1291669] 1
ThreadDemo[5540:1291682] 没有了,取光了

我们发现6被取出来两次(因为代码简单,执行效率较快,所以这种情况不是必现,耐心多试几次),这样就尴尬了,一张票卖了2次。线程锁可以解决这一问题,我们就讲最直接的两种(之前说的GCD的很多方法同样可以等价于线程锁解决这些问题):

NSLock

代码这样写:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.lock = [[NSLock alloc] init];
    self.sourceArray_m = [NSMutableArray new];
    [_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
    [self threadLock];
}
-(void)threadLock{
    for (int i = 0; i < 8; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"%@",[self sourceOut]) ;
        });
    }
}
-(NSString*)sourceOut{
    NSString *source = @"没有了,取光了";
    [_lock lock];
    if (_sourceArray_m.count > 0) {
        source = [_sourceArray_m lastObject];
        [_sourceArray_m removeLastObject];
    }
    [_lock unlock];
    return source;
}

运行结果:

ThreadDemo[5593:1298144] 5
ThreadDemo[5593:1298127] 6
ThreadDemo[5593:1298126] 4
ThreadDemo[5593:1298129] 3
ThreadDemo[5593:1298146] 2
ThreadDemo[5593:1298144] 1
ThreadDemo[5593:1298127] 没有了,取光了
ThreadDemo[5593:1298147] 没有了,取光了

这样就保证了被Lock的资源只能同时让一个线程进行访问,从而也就保证了线程安全。

@synchronized

这个也很简单,有时候也会用到这个,要传入一个同步对象(一般就是self),然后将你需要加锁的资源放入代码块中,如果该资源有线程正在访问时,会让其他线程等待,直接上代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.sourceArray_m = [NSMutableArray new];
    [_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
    [self threadLock];
}
-(void)threadLock{
    for (int i = 0; i < 8; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"%@",[self sourceOut]) ;
        });
    }
}
-(NSString*)sourceOut{
    NSString *source = @"没有了,取光了";
    @synchronized (self) {
        if (_sourceArray_m.count > 0) {
            source = [_sourceArray_m lastObject];
            [_sourceArray_m removeLastObject];
        }
    }
    return source;
}

运行结果:

ThreadDemo[5625:1301834] 5
ThreadDemo[5625:1301835] 6
ThreadDemo[5625:1301837] 4
ThreadDemo[5625:1301852] 3
ThreadDemo[5625:1301834] 1
ThreadDemo[5625:1301854] 2
ThreadDemo[5625:1301835] 没有了,取光了
ThreadDemo[5625:1301855] 没有了,取光了

参考文章:
https://www.jianshu.com/p/51fd1362249e
http://www.cocoachina.com/articles/20404

上一篇下一篇

猜你喜欢

热点阅读