首页投稿(暂停使用,暂停投稿)iOS学习笔记iOS学习开发

多线程- NSThread、GCD、NSOperation/NS

2017-09-06  本文已影响218人  寻形觅影

一、多线程简介:

所谓多线程是指一个 进程 -- process可以理解为系统中正在运行的一个应用程序)中可以开启多条 线程 -- thread线程是进程的基本执行单元,一个进程的所有任务都在线程中执行,每1个进程至少要有1条线程),多条线程可以同时执行不同的任务 -- task。CPU每一个核同一时间只能执行一条线程。多线程对于单核来讲就是让CPU快速的在多个线程之间进行调度(并发)。而多核处理系统可以让多个线程实现真正的同时进行(并行)。

多线程优点:

多线程缺点:

在iOS中,一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”,主线程的主要作用是显示\刷新UI界面、处理UI事件,如点击、滚动、拖拽等事件。需要注意的是:别将比较耗时的操作放到主线程中,会影响程序流畅度。

iOS中常见的多线程技术一般有三个:NSThread、NSOperation、GCD,其中GCD是目前苹果官方比较推荐的方式。

二、NSThread:

NSThread可以直接操控线程对象,非常直观和方便。但是它的生命周期有时需要我们手动管理,一般用来查看当前线程是否是主线程。下面来看看它的一些常见用法:

// 以下服务质量(QoS)用于向系统表明工作的性质和重要性。 它们被系统用于管理各种资源。
// 资源争夺期间,较高的QoS类别获得更多的资源(除default外优先级依次降低)
typedef NS_ENUM(NSInteger, NSQualityOfService) {
    //用于直接涉及提供交互式UI的工作,例如处理事件或绘图到屏幕
    NSQualityOfServiceUserInteractive = 0x21,
    
    //用于执行用户明确请求的工作,并且必须立即呈现哪些结果,以便进一步进行用户交互。
    NSQualityOfServiceUserInitiated = 0x19,
    
    //用于执行用户不太可能立即等待结果的工作。
    //这项工作将以节能的方式运行,在资源受到限制时尊重更高的QoS工作。
    NSQualityOfServiceUtility = 0x11,
    
    //用于非用户启动或可见的工作。
    //它将以最有效的方式运行,同时最大限度地尊重更高的QoS工作。
    NSQualityOfServiceBackground = 0x09,

    //表示缺少QoS信息。每当有可能的QoS信息,将从其他来源推断。
    //如果这样的推断是不可能的,则使用UserInitiated和Utility之间的QoS。
    NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);

基于NSThread来实现的NSObject的扩展方法:

以上四个方法常用于线程间的通讯


二、GCD:

Grand Central Dispatch (GCD)是异步执行任务的技术之一,它使用了非常简洁的方法实现了极为复杂繁琐的多线编程。

1、队列的理解:

调度队列一般有两种类型:

除了自己创建队列外,还可以获取系统提供的队列:

2、GCD中有2个用来执行任务的函数:

关于同步、异步、并发、串行的概念,简单来讲同步异步是针对是否开辟新线程来讲的,而并发串行是针对队列中任务的执行顺序来讲的,关于具体概念及关系:

🍎 系统全局队列 系统主队列 creat串行队列 creat并发队列
同步(sync),均不开辟新线程,在本线程上运行 不能这样使用 不能这样使用,会造成死锁!!! 在某一线程上串行 在某一线程上串行
异步(async) 每次都会开辟新线程,并发 不开辟新线程,在某一线程上串行,独立直接使用就是主线程上 会开辟一条新线程,在新的线程上串行 每次都会开辟新线程,并发

可以如下示例一样去验证以加深理解:

    NSLog(@"主线程 == %@", [NSThread currentThread]);
    // 调度队列类型 dispatch_queue_t
    dispatch_queue_t serialQueue, concurrentQueue, mainQueue, globalQueue;
    // 调度队列创建函数创建串行队列
    serialQueue = dispatch_queue_create("com.atx610.NewMTX.serialQueue", DISPATCH_QUEUE_SERIAL);
    // 调度队列创建函数创建并行队列
    concurrentQueue = dispatch_queue_create("com.atx610.NewMTX.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    // 获取主队列
    mainQueue = dispatch_get_main_queue();
    // 获取全局队列
    globalQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    
    dispatch_async(serialQueue, ^{
        NSLog(@"异步,串行 == %@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"异步,串行 == %@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"异步,串行 == %@", [NSThread currentThread]);
    });
    
    dispatch_sync(serialQueue, ^{
        NSLog(@"同步,串行 == %@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"同步,串行 == %@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"同步,串行 == %@", [NSThread currentThread]);
    });
3、调整队列的优先级

对于主队列和全局队列的优先级是一般是无法更改的,如果更改会发生不可预知的状况,但不一定会崩溃。一般调整只是针对使用dispatch_queue_creat ()函数创建的队列,且一般更改的是将要进行异步的并发的对列。默认生成的队列优先级为默认优先级,一般操作不立即生效,但是如果队列处于未激活状态则会立即生效。

4、时间函数的应用

在GCD中时间一般使用dispatch_time_t类型来表示,系统提供了两个相对时间常量:

(1)、创建相对时间:
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);函数相对于默认时钟创建新的dispatch_time_t或修改现有的dispatch_time_t

#define NSEC_PER_SEC 1000000000ull // 1s
#define NSEC_PER_MSEC 1000000ull // 1ms
#define USEC_PER_SEC 1000000ull // 1ms
#define NSEC_PER_USEC 1000ull // 1µm

//举例,表示距离现在10秒后时间点
dispatch_time_t newTime = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);

(2)、创建绝对时间:
dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta); 这一函数常用来创建基于一个绝对时间的绝对时间,例如想得到2018年8月8号8时8分8秒向后再添加8秒的时间。

#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC    struct timespec
_STRUCT_TIMESPEC
{
    __darwin_time_t tv_sec;
    long            tv_nsec;
};
#endif 

使用:

    NSString * dateString = @"2018-08-08 08:08:08";
    NSDateFormatter * formatDate = [[NSDateFormatter alloc] init];
    formatDate.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    NSDate * yearDate = [formatDate dateFromString:dateString];
    NSTimeInterval interval = [yearDate timeIntervalSince1970];
    
    double second, subSecond;
    struct timespec time;
    // 将浮点数分解为整数和小数
    subSecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_nsec = subSecond * NSEC_PER_SEC;
    dispatch_time_t absoluteTime = dispatch_walltime(&time, 8 * NSEC_PER_SEC);
// 这样就获得了一个相对于具体时间的绝对时间

(3)、延迟函数:
iOS中做一个延迟一段时间的方法有很多,其中在GDC中可以使用void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);函数来做延迟操作。

首先来举个🌰:

    dispatch_time_t newTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
    dispatch_after(newTime, dispatch_get_main_queue(), ^{
        NSLog(@"三秒后的我~");
    });
5、调度组 -- Dispatch groups

Dispatch groups是在一个或多个任务执行完成之前阻止线程的一种方式。类似于将所有任务加入到串行队列中,当最后一个任务执行完毕后再做下一步操作,但是不同的是Dispatch groups一般针对异步并发队列,因为你无法检测几个任务谁是最后执行完的,但是这几个任务不全部完成就无法进行下一步操作,或者下一步操作出现未知状况,Dispatch groups就是解决这样的问题的。

以上两个方法是配合dispatch_group_async ()一起使用的


示例:

    dispatch_group_t newGroup = dispatch_group_create();
    dispatch_queue_t queue_change = dispatch_queue_create("com.atx610.NewMTX.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(newGroup, queue_change, ^{
        sleep(1);
        NSLog(@"test_1");
    });
    dispatch_group_async(newGroup, queue_change, ^{
        NSLog(@"test_2");
    });

    dispatch_group_enter(newGroup);
    dispatch_async(queue_change, ^{
        NSLog(@"test_3");
        dispatch_group_leave(newGroup);
    });
    
    dispatch_group_enter(newGroup);
    dispatch_async(queue_change, ^{
        sleep(2);
        NSLog(@"test_4");
        dispatch_group_leave(newGroup);
    });

    dispatch_group_notify(newGroup, queue_change, ^{
        NSLog(@"done,and start new work");
    });

//    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
//    long result = dispatch_group_wait(newGroup, time);
//    if (result == 0) {
//        NSLog(@"在设置的时间内,组中的任务全部完成");
//    }else{
//        NSLog(@"在设置的时间内,组中的任务未全部完成");
//    }

结果:

test_2
test_3
test_1
test_4
done,and start new work
6、调度障碍 -- Dispatch Barrier

调度障碍API是一种向dispatch queue提交障碍Block的机制,类似于dispatch_async()/ dispatch_sync()API。主要用于在访问数据库或者文件时,实现高效的读/写方案。障碍Block 仅在提交到使用DISPATCH_QUEUE_CONCURRENT属性 创建的 队列(并发队列)时才有特殊的作用; 在个队列上,只有在障碍Block前添加到队列中的所有任务都已经完成,障碍Block才会运行,而在障碍Block完成前,障碍Block之后提交到队列的任何任务都将不会运行,直到障碍Block完成。
 需要特别注意的是:当提交到全局队列(global queue)或 未使用 DISPATCH_QUEUE_CONCURRENT属性创建的队列时,障碍Block与dispatch_async()/ dispatch_sync()API提交的Block的行为相同!!!

示例:

    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.atx610.NewMTX.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"test_1");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"test_2");
        sleep(2);
        NSLog(@"睡醒了~");
    });
    
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"I Am Barrier!");
    });
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"test_3");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"test_4");
    });

使用create函数创建的队列运行结果如下:

test_1
test_2
睡醒了~
I Am Barrier!
test_3
test_4

使用全局队列运行结果如下:

test_1
test_2
I Am Barrier!
test_3
test_4
睡醒了~
7、与执行次数相关的函数:

(1)、dispatch_apply 函数:
 该函数功能:将一个任务提交给调度队列进行多次调用。此函数在返回之前等待任务块完成,即 会将指定次数的指定Block追加到指定的队列中并等待全部处理执行结束! 如果目标队列是并发的,则该块可能会同时被调用。

常见使用方式:

    dispatch_queue_t globalQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    dispatch_async(globalQueue, ^{
        dispatch_apply(5, globalQueue, ^(size_t index) {
            // 在次并列处理某些事件
            NSLog(@"%zu", index);
        });
        NSLog(@"apply循环 结束");
        // 只有apply中的事件全部处理完毕了才会进行后面的操作
        dispatch_async(dispatch_get_main_queue(), ^{
            // 主队列更新界面等操作
            NSLog(@"主队列更新");
        });
    });
0
1
2
3
4
apply循环 结束
主队列更新

(2)、dispatch_once 函数:
 dispatch_once_t 类型来定义一个变量,dispatch_once 函数来保证在应用程序中只执行一次指定处理的任务。

+ (instancetype)shareManager {
    static dispatch_once_t onceToken;
    static Manager * manager;
    dispatch_once(&onceToken, ^{
        manger = [[self alloc] init];
    });
    return manager;
}
8、调度信号量 -- Dispatch Semaphore

调度信号量用dispatch_semaphore_t表示,调度信号量与传统信号量相似,但通常效率更高。 调度信号量只有当因为信号量不可用而线程需要被阻塞时才调用到内核。 如果信号量可用,则不进行内核调用。

使用:

    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semap = dispatch_semaphore_create(0);
    dispatch_async(global, ^{
        dispatch_semaphore_wait(semap, DISPATCH_TIME_FOREVER);
        NSLog(@"first");
    });
    NSLog(@"second");
    dispatch_semaphore_signal(semap);

结果:

second
first
9、其他常见使用GCD函数

三、NSOperation / NSOperationQueue:

操作队列是一种面向对象的方法来封装要异步执行的工作,显示如何使用Objective-C对象来封装和执行任务。 NSOperation本身是抽象类,只能使用其子类,另外Foundation框架提供了两个具体的子类:NSBlockOperationNSInvocationOperation,除此之外还可以自定义继承自NSOperation的类并实现内部相应的方法来使用NSOperation。一般NSOperation和NSOperationQueue结合使用实现多线程并发。

1、关于抽象类NSOperation:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

-@property (nullable, copy) void (^completionBlock)(void);:任务操作完成后的Block

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    NSQualityOfServiceUserInteractive = 0x21,
    NSQualityOfServiceUserInitiated = 0x19,
    NSQualityOfServiceUtility = 0x11,
    NSQualityOfServiceBackground = 0x09,
    NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);

2、创建NSInvocationOperation对象:
 主要是使用- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;这一方法,在你指定的对象上调用你指定的方法,还可以传递一个参数。如下所示:

NSString * name = @"lucy";
NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logSomethingWithString:) object:name];
[invocationOperation start];
--------------
- (void)logSomethingWithString:(NSString *)name
{
    NSLog(@"%@", name);
}

3、创建NSBlockOperation对象:
 相对而言NSBlockOperation要比NSInvocationOperation好用一点,该类有两个方法和一个属性:

示例:

    NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"001");
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"002");
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"003");
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"004");
    }];
    [blockOperation start];
    NSLog(@"%@", blockOperation.executionBlocks);

4、NSOperationQueue -- 操作队列:
 操作队列提供一个异步执行的线程,同NSOperation配合使用可以高效利用管理操作和线程。

示例:

    - (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    imageViewArray = [NSMutableArray array];
    for (int i = 0; i < 3; i++) {
        UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100 + 100*i, 100, 100)];
        [imageViewArray addObject:imageView];
        [self.view addSubview:imageView];
    }
    NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"001 --- 001, thread == %@", [NSThread currentThread]);
        [imageViewArray[0] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504690123016&di=00957249b318d00f04c80439063671e0&imgtype=0&src=http%3A%2F%2Finuyasha.manmankan.com%2FUploadFiles_6178%2F201004%2F20100427165757723.jpg"]]]];
    }];
    
    NSBlockOperation * blockOperation_copy = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"copy --- 001, thread == %@", [NSThread currentThread]);
        [imageViewArray[1] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504690048974&di=ddc4518682a2e884cd9e88bbf7d9aa00&imgtype=0&src=http%3A%2F%2Fimg.qqu.cc%2Fuploads%2Fallimg%2F150601%2F1-150601201258.jpg"]]]];
    }];
    
    NSBlockOperation * blockOperation_black = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"black --- 001, thread == %@", [NSThread currentThread]);
        [imageViewArray[2] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504689993435&di=c52ca4a5d5f46fc2552d3e97dd4eb241&imgtype=0&src=http%3A%2F%2Fp4.gexing.com%2Fshaitu%2F20120825%2F1336%2F5038647615f85.jpg"]]]];
    }];
    // 添加依赖关系
    [blockOperation addDependency:blockOperation_copy];
    [blockOperation_copy addDependency:blockOperation_black];
    queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    [queue addOperations:@[blockOperation, blockOperation_copy, blockOperation_black] waitUntilFinished:NO];
    [queue setSuspended:YES];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [queue setSuspended:NO];
}
效果图.gif
上一篇 下一篇

猜你喜欢

热点阅读