iOS 多线程开发

作者: 追梦赤子心Year | 来源:发表于2017-02-16 14:06 被阅读156次

作为一个开发人员, 有两个词无论是工作中还是面试中, 都会经常听见, 被问及:"进程""线程"。 在开始了解多线程之前, 先来了解一下这二者的关系和区别: 简单点说, 进程是一个有独立地址空间的, 而线程只是一个进程中执行任务的一个路径, 这是二者有本质的区别, 可以说他们是不同的操作系统资源的管理方式。

做iOS开发,很少会涉及到进程。我们一般会做的就是进程间通信,从当前App跳转到第三方App,例如“QQ”、“微信”或指定App。我们可能会在跳转过程中传递一些参数,在第三方App上做出相应的操作,这就是我目前涉及到的跟进程有关的开发。这里我主要还是想讲的是线程,因为,相比之下,线程在我们开发过程中还是尤为重要的。

线程

可以说,一个进程是由一个或多个线程组成的,进程负责调度多条线程, 真正执行任务,负责代码执行的是线程。

在程序被启动的时候,系统的主线程就被创建,直到程序完全退出,这个线程都会存在。他用来执行main函数。这时的主线程要完成所有的操作,网络请求、UI展示、动画等等。在只有主线程的情况下这些任务都要按顺序一个一个执行, 无法并发执行。所以,我们需要多线程来协助主线程完成这个并发执行的任务。

iOS多线程

多线程很好理解,有多条线程在执行任务。

它其实是针对单核的CPU设计的, 因为单核的同一时间只能执行一个任务, 但是我们可以利用多线程,让CPU快速的在多个线程之间切换,从而给我们一种多个任务在同时进行的错觉。在多核的CPU中, 是真实的达到了, 多个线程同时执行任务。

1. 多线程优缺点

优点:① 适当的提高代码执行效率

           ② 适当提高资源利用率

缺点:① 开启线程过多,会占用大量内存空间,所以要适度

           ② 程序设计更复杂,需要考虑诸如线程安全的问题

2. iOS中的多线程

iOS中多线程主要有四种:NSThread、NSObject、NSOperationQueue、GCD

2.1  NSTread 线程类

优点:1. 轻量级

            2.可以快速创建子线程,并对子线程有控制权

缺点:1. 需要手动管理线程的生命周期

            2.要手动开启子线程、基本信息要手动设置

2.1.1 NSTread的创建

NSTread的创建有三种方式, 后两者相当于对第一种进行了封装,我们不再可以设置线程的基本信息

基本的创建方式:

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTask) object:nil];

thread.name = @"线程类";

[thread start];

快速创建,并自动开启:

[NSThread detachNewThreadSelector:@selector(threadTask) toTarget:self withObject:nil];

隐式创建,并自动开启:

[self performSelectorInBackground:@selector(threadTask) withObject:nil];

这里面线程我让他执行threadTask里面的任务, 里面模拟了线程卡死代码,并看看当前是在哪执任务

- (void)threadTask {

if ([NSThread isMainThread]) {

NSLog(@"当前线程 是 主线程,是%@", [NSThread currentThread]);

} else {

NSLog(@"当前线程 非 主线程,是%@", [NSThread currentThread]);

}

NSInteger num = 0;

for (NSInteger i = 0; i < 1000000000; i++) {

num++;

}

NSLog(@"%ld", num);

[self performSelector:@selector(reloadUIView) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];

}

这里面用到了几个线程相关的方法:

isMainThread : 查看当前线程是否是主线程(0:非  /  1:是)

currentThread: 查单当前线程

mainThread:     获得主线程

线程间通信:

在某个线程或主线程执行某个任务

-(void)performSelector:(SEL)aSelectoron Thread:(NSThread*)thread withObject:(id)arg waitUntilDone:(BOOL)wait;

-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

2.2 GCD

GCD是iOS开发中应用最为广泛的多线程开发技术,使用得最简单。GCD是苹果公司自己开发的,基于C语言写的,提供了非常强大的函数。

想学懂GCD,不得不先了解一下任务和队列两个核心概念

2.2.1 任务和队列

一、概念:

        任务:执行的操作,通常是我们代码中的方法

        队列:可以理解为用来存放任务的列表

二、分类:

        任务的种类(是否开辟新的线程):

                     同步执行(不开辟新线程),异步执行(开辟新线程)

        队列的种类(任务的执行方式):

                    并行队列(多个任务可同时执行),串行队列(任务按顺序一个一个执行)

三、组合:

        同步执行+并行队列

        同步执行+串行队列

        异步执行+并行队列

        异步执行+串行队列

    还有一种队列叫主队列,是串行。加上这种队列,又有两种新组合方式

        同步执行+主队列

        异步执行+主队列

2.2.2 GCD的使用步骤

1->创建队列:

    并行队列:

dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);

    串行队列:

dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);

    主队列:

dispatch_queue_t queue = dispatch_get_main_queue();

   全局队列:

dispatch_queue_t globa = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

2->创建任务:

    同步执行:

dispatch_sync(queue, ^{

});

    异步执行:

dispatch_async(queue, ^{

});

2.2.3 GCD死锁

GCD死锁的问题我也是绕了好久才绕明白。这里来列举几个会造成死锁的案例:

案例1. 主队列+同步执行

// 同步主队列

- (void)syncMainQueue {

NSLog(@"~~~1~~~");//任务1

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"~~~2~~~");//任务2

});

NSLog(@"~~~3~~~");//任务3

}

执行结果:

分析:进入到方法中,首先执行任务1,接下来遇到同步线程的任务2,队列是先进先出的原则,由于三个任务全都是在主队列上,同步的任务2实际上是插在了任务3的后面,但是任务3却要等任务2执行完毕之后才会执行,这样就形成了任务2和任务3互等的状态,无法往下执行,卡死在这里。

解决:自定义串行队列,配合同步线程

// 同步串行

- (void)syncSerial {

NSLog(@"~~~1~~~");

dispatch_sync(dispatch_queue_create("同步串行", DISPATCH_QUEUE_SERIAL), ^{

NSLog(@"~~~2~~~");

});

NSLog(@"~~~3~~~");

}

执行结果:

分析:

主队列也是串行队列,同样是同步线程,但结果却是不一样的。造成执行结果不同的唯一原因就是任务2是执行在一个不同于任务1和任务3的队列中。

来看一下执行顺序,首先执行任务1,接着遇到同步线程的任务2,在任务2执行完毕之后,去执行任务3。由于不是在一个队列中,任务2是排在了自定义的队列的第一个位置, 不用等任务3执行完就可以去执行。、

案例2:在自定义队列中同步执行

- (void)asyncAndSync {

NSLog(@"~~~1~~~");

dispatch_queue_t queue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);

dispatch_async(queue, ^{

NSLog(@"~~~2~~~");

dispatch_sync(queue, ^{

NSLog(@"~~~3~~~");

});

NSLog(@"~~~4~~~");

});

NSLog(@"~~~5~~~");

}

执行结果:

任务3,任务4未执行

分析:

  首先看看5个任务都是在哪个队列执行的 任务1-主队列、任务2-自定义队列、任务3-自定义队列、任务4-自定义队列、任务5-主队列。

再来看看任务的执行顺序:首先执行任务1;接着遇到异步任务,暂时跳过,执行任务5;接着进入到异步任务内,执行任务2;接下来遇到同步任务3,但是任务3,任务2,任务4都是执行在自定义的队列里,所以他排在了任务4后,但是任务4还在等同步队列执行完才执行, 所以两者进入了互等状态,卡死在这里。

2.2.4 GCD对比

2.3 NSOperation 操作类

单独的NSOperation对象是不能进行多线程编辑的,它需要配合NSOperationQueue(操作队列)来实现多线程

首先我们应该知道,NSOperation是一个抽象类, 它本事并不具备封装操作的能力, 所以我们必须创建NSOperation的子类

2.3.1 创建NSOperation

NSOperation的子类有三种:NSInvocationOperationNSBlockOperation和自定义的继承于NSOperation的类。

2.3.2 NSOperation对象的创建与使用

1-> NSInvocationOperation

- (void)invocationOperation {

NSInvocationOperation *operation_1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logNum) object:nil];

[operation_1 start];

NSInvocationOperation *operation_2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logNum) object:nil];

[operation_2 start];

}

2-> NSBlockOperation

       这里创建NSBlockOperation对象的方法:

              +(id)blockOperationWithBlock:(void(^)(void))block;

       添加操作:

             -(void)addExecutionBlock:(void(^)(void))block;

- (void)blockOperation {

NSBlockOperation *operation_1 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"~~~1.%@~~~", [NSThread currentThread]);

}];

[operation_1 addExecutionBlock:^{

NSLog(@"~~~2.%@~~~", [NSThread currentThread]);

}];

[operation_1 addExecutionBlock:^{

NSLog(@"~~~3.%@~~~", [NSThread currentThread]);

}];

[operation_1 addExecutionBlock:^{

NSLog(@"~~~4.%@~~~", [NSThread currentThread]);

}];

[operation_1 addExecutionBlock:^{

NSLog(@"~~~5.%@~~~", [NSThread currentThread]);

}];

[operation_1 start];

}

执行结果:

多打印几次会发现,执行操作的线程是不确定的;

3->自定义类

自定义的继承于NSOperation的类需要重写main方法

2.3.3 NSOperationQueue

// 创建队列

NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];

// 设置最大并发数(同时可处理的线程的个数)

[queue setMaxConcurrentOperationCount:2];

// 创建任务

MyOperation *op1 = [[MyOperation alloc] init];

MyOperation *op2 = [[MyOperation alloc] init];

// 添加到队列

[queue addOperation:op1];

[queue addOperation:op2];

相关文章

网友评论

    本文标题:iOS 多线程开发

    本文链接:https://www.haomeiwen.com/subject/smoxwttx.html