iOS多线程方案总结及使用详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS多线程方案总结及使用详解相关的知识,希望对你有一定的参考价值。

本篇文章整理了几种ios中主要的多线程方案,提供了Swift和Objective-C两种语言的写法。

概述

iOS目前有四种多线程解决方案:

  • NSThread
  • GCD
  • NSOperation
  • Pthread

Pthread这种方案太底层啦,实际开发中很少用到,下文主要介绍前三种方案

NSThread

NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同步等问题。

创建方法

Objective-C:

线程的创建与启动

// 初始化线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadFunction:) object:nil];

// 启动线程
[thread start];

或者创建后直接启动

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

 Swift:

//创建
let thread = NSThread(target: self, selector: "threadFunction:", object: nil)

//启动
thread.start() 

 或者

NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)

OC中还有一种使用NSObject的方法创建线程并直接启动的方法

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

Swift中由于安全问题,苹果去掉了这个方法。

其他一些常用的方法

//获取当前线程信息
+ (NSThread *)currentThread;

//获取主线程信息
+ (NSThread *)mainThread;

//取消线程
- (void)cancel;

//获取线程状态
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

Swift的方法名和调用方式和OC基本一致,这里就不一一列举了。

GCD

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。

GCD是一个替代诸如NSThread等技术的很高效和强大的技术。GCD完全可以处理诸如数据锁定和资源泄漏等复杂的异步编程问题。GCD的工作原理是让一个程序,根据可用的处理资源,安排他们在任何可用的处理器核心上平行排队执行特定的任务。这个任务可以是一个功能或者一个程序段。

简单的说,GCD会自动的管理线程生命周期(创建、调度、销毁),我们只需要告诉它该做什么,其他的事它都会帮我们搞定,是不是很方便?

要了解GCD的使用方法,首先要明白四个概念:任务、队列、同步执行、异步执行

任务:在GCD中任务可以被理解为一段Block代码,即你想要执行的操作。

同步执行(同步派发):任务在同一线程内按顺序一个一个执行,前一个任务执行完毕后下一个任务才会开始。换言之,当前线程会被阻塞。

异步执行(异步派发):任务在不同的线程中执行,下一个任务不用等待前一个任务执行完毕后才开始。换言之,当前线程不会被阻塞。

队列:用来放置任务的资源池,并按照一定的规则取出并执行任务。队列分为串行队列并行队列

  • 串行队列:遵循FIFO(先进先出)原则一个一个取出任务并执行,前一个任务完成后再取出下一个任务并执行。无论是同步还是异步执行,串行队列都是一个一个执行任务,也就是说同一个串行队列中的任务都是在同一个线程中执行的。
  • 并行队列:同样遵循FIFO原则一个一个取出任务,与串行队列的区别在于取出一个任务就会新开一个线程并在新线程中执行。同一个并行队列中的任务一般都在不同的线程中执行

队列的执行方式不同,产生的效果不同

  同步执行 异步执行
串行队列 阻塞当前线程,直到串行队列中的任务都执行完毕。系统不会另起线程,会在当前线程按顺序一个一个地执行任务。 不会阻塞当前线程,系统会另起一个新线程,在新线程中按顺序一个一个地执行任务。
并行队列 阻塞当前线程,直到并行队列中的同步派发任务都执行完毕。系统不会另起线程,会在当前线程按顺序执行同步派发的任务。 不会阻塞当前线程,系统会另起新线程并执行,每多一个异步派发的任务系统就会多开一个线程。

 

死锁的形成

当以同步派发的方式往当前线程所在的串行队列添加任务时,就会形成死锁。因为同步派发会阻塞当前线程,直到派发的任务执行完成线程才会继续执行。而派发的任务又恰好是加入的当前被阻塞的线程,这样会导致任务无法被执行(因为任务所在线程已被阻塞),于是形成死锁。

创建队列

1、主队列

这是系统定义的串行队列,任何会刷新UI的任务都要在主队列中进行。因此,尽量不要在主队列中添加很耗时的任务,确保刷新界面的任务不会被阻塞,给用户以流畅的交互体验。

//Objective-C
dispatch_queue_t queue = ispatch_get_main_queue();

//SWIFT
let queue = ispatch_get_main_queue()

 2、自定义队列

可以创建串行或并行队列,第二个参数传DISPATCH_QUEUE_SERIAL或NULL为串行队列,传DISPATCH_QUEUE_CONCURRENT为并行队列。

//OBJECTIVE-C
//串行队列
dispatch_queue_t queue = dispatch_queue_create("serialTestQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("serialTestQueue", DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrentTestQueue", DISPATCH_QUEUE_CONCURRENT);

//SWIFT
//串行队列
let queue = dispatch_queue_create("serialTestQueue", nil);
let queue = dispatch_queue_create("serialTestQueue", DISPATCH_QUEUE_SERIAL)
//并行队列
let queue = dispatch_queue_create("concurrentTestQueue", DISPATCH_QUEUE_CONCURRENT)

3、全局并行队列

这是系统定义的并行队列,并行任务一般都加入这个队列中执行。

//OBJECTIVE-C
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//SWIFT
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

创建任务

1、同步派发(sync)

同步派发会阻塞当前线程。不管是往串行还是并行队列中同步派发任务,都不会新开线程,只会在当前线程中执行任务。注意,如果当前线程是在一个串行队列中执行,则不能在当前线程中向此队列同步派发任务,否则会形成死锁。

// Objective-C
dispatch_sync(<#传入队列#>, ^{
      //执行代码块
});

// SWIFT
dispatch_sync(<#传入队列#>, { () -> Void in
      //执行代码块
})

2、异步派发(async)

异步派发不会阻塞当前线程。往非当前线程所在队列异步派发任务时,系统一定会另起新线程;往当前线程所在队列异步派发任务时有两种情况:1、如果当前线程所在队列是串行队列,系统不会另起新线程,添加的任务会在队列中前面的任务都执行完毕后才会执行(FIFO);2、如果当前线程所在队列是并行队列,系统会另起新线程,添加的任务会与队列中的其他任务同时执行。

// Objective-C
dispatch_async(<#传入队列#>, ^{
      //执行代码块
});

// SWIFT
dispatch_async(<#传入队列#>, { () -> Void in
      //执行代码块
})

队列组

队列组可以将不同类型的队列添加到一个组里,当所有队列都执行完毕的时候有一个回调方法:

//Objective-C
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"队列中的任务都已完成");
});

//SWIFT
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
    NSLog("对了中的任务都已完成")
}

其他实用方法

func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

当第一个参数传入的是自定义的并行队列时,添加的任务会阻塞这个队列,当排在它前面的任务都执行完毕后再开始执行这个任务,并且当这个任务执行完毕后才会取消队列的阻塞状态;当第一个参数传入值为其他情况,此方法的作用和dispatch_async一样。

func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

当第一个参数传入的是自定义的并行队列时,这个方法和上一个方法作用类似,区别在于此方法还会阻塞当前线程;当第一个参数传入值为其他情况,此方法的作用和dispatch_sync一样。

NSOperation

NSOperation其实就是对GCD用面向对象的方式进行的封装。

任务->NSOperation      队列->NSOperationQueue

添加任务

NSOperation是一个基类,它有两个子类:NSInvocationOperation 和 NSBlockOperation。创建任务后需要调用start方法来启动任务,默认再当前队列同步执行。可以在中途调用cancel方法中止任务的执行。

NSInvocationOperation

Objective-C

//1.创建对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationBlock) object:nil];

//2.开始执行
[operation start];

因为安全问题,Swift不能使用这个类

NSBlockOperation

//Objective-C
//1.创建对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//2.开始任务
[operation start];

//--------------------------------------------------//
//SWIFT //1.创建对象 let operation = NSBlockOperation { () -> Void in println(NSThread.currentThread()) } //2.开始任务 operation.start()

那如何并行执行任务呢?NSBlockOperation 有一个方法:addExecutionBlock:可以给一个Opertion添加多个任务,这些任务会并行执行

Objective-C:

//1.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//添加多个Block
for (NSInteger i = 0; i < 5; i++) {
    [operation addExecutionBlock:^{
          NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
    }];
}

//2.开始任务
[operation start];

SWIFT:

//1.创建NSBlockOperation对象
let operation = NSBlockOperation { () -> Void in
      NSLog("%@", NSThread.currentThread())
}

//2.添加多个Block
for i in 0..<5 {
      operation.addExecutionBlock { () -> Void in
             NSLog("第%ld次 - %@", i, NSThread.currentThread())
      }
}

//3.开始任务
operation.start()

注意:addExecutionBlock 方法必须在 start() 方法之前执行,否则就会报错。

自定义Operation

除了上面的两种 Operation 以外,还可以自定义 Operation。继承 NSOperation 类,并实现其 main 方法,因为在调用 start 方法的时候,内部会调用 main方法完成相关逻辑。除此之外,你还需要实现 cancel在内的其他方法。

创建队列

调用operation的start方法是同步执行的,也就是说它会阻塞当前线程,那怎么用异步执行的方式呢?这里就要使用队列了NSOperationQueue

主队列

这是系统定义的队列,任何会刷新UI的任务都要在主队列中进行。为了能给用户流程的UI体验,主队列中的线程优先级是最高的。这个队列中的任务都是串行执行的。

//OBJECTIVE-C
NSOperationQueue *queue = [NSOperationQueue mainQueue];

//SWIFT
let queue = NSOperationQueue.mainQueue()

其他队列

自定义的队列,就是其他队列。默认情况下其他队列的任务会在不同的线程中并行执行。它有一个参数maxConcurrentOperationCount最大并发数,用来设置最多能有多少个并行任务同时执行。把它设置成1就表明这是一个串行队列。

//Objective-C 创建其他队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//Swift 创建其他队列
let queue = NSOperationQueue()

添加任务

// Objective-C 往队列里添加任务
[queue addOperation:operation];

// Swift 往队列里添加任务
queue.addOperation(operation)

 往队列里添加任务后会自动开始执行,不用调用start。

还有一个添加任务的方法-(void)addOperationWithBlock:(void (^)(void))block; 可以直接往将想执行的代码块添加到队列中了,不用再创建一个Operation。

添加依赖项

当任务A必须要在任务B完成后才能开始执行,这时就可以添加  A依赖B。

[operationA addDependency:operationB];

注意:1、不能相互添加依赖(A依赖B,B依赖A),这样会形成死锁;2、可以使用removeDependency来解除依赖;3、可以在不同队列中的任务添加依赖。

其他方法

NSOperation

BOOL executing; //判断任务是否正在执行

BOOL finished; //判断任务是否完成

void (^completionBlock)(void); //用来设置完成后需要执行的操作

- (void)cancel; //取消任务

- (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕

NSOperationQueue

NSUInteger operationCount; //获取队列的任务数

- (void)cancelAllOperations; //取消队列中所有的任务

- (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕

[queue setSuspended:YES]; // 暂停queue

[queue setSuspended:NO]; // 继续queue

线程同步

当有一个线程在对内存中的某一资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作。这样做主要是为了保证一些资源数据的安全性和可靠性。

实现线程同步可以采用以下方式:

互斥锁

//Objective-C
@synchronized(self) {
    //需要执行的代码块
}

//SWIFT
objc_sync_enter(self)
//需要执行的代码块
objc_sync_exit(self) 

同步执行

可以使用上文介绍的串行队列知识,把每一个要访问此段资源的代码都添加到同一个串行队列中,这样就可以实现线程同步。

总结

第一次写博客,把自己以前用到的知识进行归纳总结,还是挺有用的。以后还会写更多东西分享给大家,同时也方便自己查阅。

 

感谢简书上的作者伯恩的遗产(http://www.jianshu.com/p/0b0d9b1f1f19)给了我很多帮助。



以上是关于iOS多线程方案总结及使用详解的主要内容,如果未能解决你的问题,请参考以下文章

起底多线程同步锁(iOS)

Java多线程中joinyieldsleep方法详解

iOS开发之再探多线程编程:Grand Central Dispatch详解

关于iOS多线程的总结

线程学习知识点总结

iOS多线程GCD详解