iOS中目前有4套多线程方案,他们分别是:
- Pthread
- NSThread
- GCD
- NSOperation & NSOperationQueue
Pthreads
定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。简单地说,这是一套在很多操作系统上都通用的多线程API,所以移植性很强,在iOS中也是可以的。但是是基于C语言框架的,使用起来这酸爽。。。仅作了解。
OBJECTIVE-C
首先要包含头文件
#import
然后创建线程,并执行任务
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
pthread_t thread;
//创建一个线程并自动执行
pthread_create(&thread, NULL, start, NULL);
}
void *start(void *data) {
NSLog(@"%@", [NSThread currentThread]);
return NULL;
}
看代码会发现它需要C语言函数,这就比较蛋疼,更蛋疼的是需要手动处理现成的各个状态的转换,即管理生命周期,比如,这段代码随谈创建了一个线程,但是并没有销毁。
NSThread
这套方案是经过苹果封装后的,面向对象。所以可以直接操控线程对象,非常直观和方便。但是,它的生命周期还是需要我们手动管理,所以这套方案也是偶尔用用,比如[NSThread currentThread]
,它可以获得当前线程类,你就可以知道当前线程的各种属性,调用十分方便。
用法:
创建并启动
- 先创建线程类,再启动
OBJECTIVE-C
//创建
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:nil];
//启动
[thread start];
SWIFT
//创建
let thread = NSThread(target: self, selector: "run:", object: nil)
//启动
thread.start()
- 创建并自动启动
OBJECTIVE-C
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
SWIFT
NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)
- 使用NSObject的方法创建并自动启动
OBJECTIVE-C
[self performSelectorInBackground:@selector(run:) withObject:nil];
SWIFT
苹果认为performSelector:
不安全,所以在Swift中去掉了这个方法。
其他用法
除了创建启动以外,NSThread还有很多用法,常见的用法如下:
OBJECTIVE-C
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//获取当前线程信息
+ (NSThread *)currentThread;
//获取主线程信息
+ (NSThread *)mainThread;
//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
SWIFT
Swift的方法名字和OC的方法名都一样,我就不浪费空间列举出来了。
其实,NSThread 用起来也挺简单的,因为它就那几种方法。同时,我们也只有在一些非常简单的场景才会用 NSThread, 毕竟它还不够智能,不能优雅地处理多线程中的其他高级概念。所以接下来要说的内容才是重点。
GCD
Grand Central Dispatch
它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程,调度任务,销毁线程),完全不需要我们管理,只需要告诉它干什么就行。它使用的也是C语言,不过由于使用了Block(Swift中的闭包),使用起来更加方便,而且灵活。所以基本上普遍使用GCD。
任务和队列
在 GCD 中,加入了两个非常重要的概念:任务和队列
任务:即操作,在GCD中就是一个Block,添加任务十分方便。任务有两种执行方式:同步执行和异步执行,他们之间的区别是:会不会阻塞当前线程,直到Block中的任务执行完毕。
如果是同步操作,会阻塞当前线程并等待Block中的任务执行完毕,然后当前线程才会继续往下运行。
如果是异步操作,当前线程会直接往下执行,不会阻塞当前线程。队列:用于存放任务。一共有两种对列,串行队列和并行队列。
串行队列中的任务汇分局队列的定义FIFO执行,一个接一个的先进先出的进行执行。
并行队列中的任务,GCD也会FIFO地取出来,但不同的是,他取出来一个就会放到别的线程, 然后再取出来一个又放到另一个线程。这样由于取的动作很快,看起来所有的任务都是一起执行的。不过,GCD会根据系统资源控制并行的数量,所以,如果任务很多,他并不会让所有任务同时执行。
创建队列
- 主队列:这是一个特殊的串行队列,用于刷新UI,所以一般耗时的任务都放在别的线程执行。
OBJECTIVE-C
dispatch_queue_t queue = ispatch_get_main_queue();
SWIFT
let queue = ispatch_get_main_queue()
- 自己创建的队列:可以为串行对列,也可以为并行队列。
第一个参数是标识符,用于DEBUG的时候表示唯一的队列,可以为空。
第二个参数用来表示创建的队列是串行的还是并行的,传入DISPATCH_QUEUE_SERIAL
或者NULL
表示穿件串行队列。传入DISPATCH_QUEUE_CONCURRENT
表示创建并行队列。
OBJECTIVE-C
//串行队列
dispatch_queue_t queue
dispatch_queue_create("tk.bourne.testQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
SWIFT
//串行队列
let queue dispatch_queue_create("tk.bourne.testQueue", nil);
let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL)
//并行队列
let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT)
- 全局并行队列:这是系统提供的一个并发队列,只要是并行任务一般都会加入到这个队列。
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)
创建任务
- 同步任务:会阻塞当前线程
OBJECTIVE-C
dispatch_sync(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
SWIFT
dispatch_sync(<#queue#>, { () -> Void in
//code here
println(NSThread.currentThread())
})
- 异步任务:不会阻塞当前线程
OBJECTIVE-C
dispatch_async(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
SWIFT
dispatch_async(<#queue#>, { () -> Void in
//code here
println(NSThread.currentThread())
})
为了更好的理解同步和异步,和各种队列的使用,下面看两个示例:
示例一:
以下代码在主线程调用,结果是什么?
NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
NSLog("sync - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())
答案:只会打印第一句:之前 -
,然后主线程就卡死了,你可以在界面上放一个按钮,你就会发现点不了了。
解释:同步任务会阻塞当前线程,然后把 Block 中的任务放到指定的队列中执行,只有等到 Block 中的任务完成后才会让当前线程继续往下运行。那么这里的步骤就是:打印完第一句后,dispatch_sync
立即阻塞当前的主线程,然后把 Block 中的任务放到main_queue
中,可是main_queue
中的任务会被取出来放到主线程中执行,但主线程这个时候已经被阻塞了,所以 Block 中的任务就不能完成,它不完成,dispatch_sync
就会一直阻塞主线程,这就是死锁现象。导致主线程一直卡死。
示例二:
以下代码会产生什么结果?
let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)
NSLog("之前 - %@", NSThread.currentThread())
dispatch_async(queue, { () -> Void in
NSLog("sync之前 - %@", NSThread.currentThread())
dispatch_sync(queue, { () -> Void in
NSLog("sync - %@", NSThread.currentThread())
})
NSLog("sync之后 - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())
答案:
2015-07-30 02:06:51.058 test[33329:8793087] 之前 -
2015-07-30 02:06:51.059 test[33329:8793356] sync之前 -
2015-07-30 02:06:51.059 test[33329:8793087] 之后 -
很明显sync - %@
和sync之后 - %@
没有被打印出来!这是为什么呢?我们再来分析一下:
分析:我们按执行顺序一步步来哦:
- 使用
DISPATCH_QUEUE_SERIAL
这个参数,创建了一个 串行队列。- 打印出
之前 - %@
这句。dispatch_async
异步执行,所以当前线程不会被阻塞,于是有了两条线程,一条当前线程继续往下打印出之后 - %@
这句, 另一台执行 Block 中的内容打印sync之前 - %@
这句。因为这两条是并行的,所以打印的先后顺序无所谓。- 注意,高潮来了。现在的情况和上一个例子一样了。
dispatch_sync
同步执行,于是它所在的线程会被阻塞,一直等到sync
里的任务执行完才会继续往下。于是sync
就高兴的把自己 Block 中的任务放到queue
中,可谁想queue
是一个串行队列,一次执行一个任务,所sync
的 Block 必须等到前一个任务执行完毕,可万万没想到的是queue
正在执行的任务就是sync
阻塞了的那个。于是又发生了死锁。所以sync
所在的线程被卡死了。剩下的两句代码自然不会打印。
队列组
队列组可以将很多队列添加到有个组里,这样做的好处是,当这个组里的所有的执行任务都执行完了,队列组会通过一个方法通知我们,。下面是使用方法,这是一个很实用的功能。
OBJECTIVE-C
//1.创建队列组
dispatch_group_t group = dispatch_group_create();
//2.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.多次使用队列组的方法执行任务, 只有异步方法
//3.1.执行3次循环
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
//3.2.主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 8; i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
//3.3.执行5次循环
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"group-03 - %@", [NSThread currentThread]);
}
});
//4.都完成后会自动通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 - %@", [NSThread currentThread]);
});
SWIFT
//1.创建队列组
let group = dispatch_group_create()
//2.创建队列
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
//3.多次使用队列组的方法执行任务, 只有异步方法
//3.1.执行3次循环
dispatch_group_async(group, queue) { () -> Void in
for _ in 0..<3 {
NSLog("group-01 - %@", NSThread.currentThread())
}
}
//3.2.主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue()) { () -> Void in
for _ in 0..<8 {
NSLog("group-02 - %@", NSThread.currentThread())
}
}
//3.3.执行5次循环
dispatch_group_async(group, queue) { () -> Void in
for _ in 0..<5 {
NSLog("group-03 - %@", NSThread.currentThread())
}
}
//4.都完成后会自动通知
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
NSLog("完成 - %@", NSThread.currentThread())
}
注意:
func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t)
:这个方法重点是你传入的 queue,当你传入的 queue 是通过DISPATCH_QUEUE_CONCURRENT
参数自己创建的 queue 时,这个方法会阻塞这个 queue(注意是阻塞 queue ,而不是阻塞当前线程),一直等到这个 queue 中排在它前面的任务都执行完成后才会开始执行自己,自己执行完毕后,再会取消阻塞,使这个 queue 中排在它后面的任务继续执行。如果你传入的是其他的 queue, 那么它就和dispatch_async
一样了。
func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t)
:这个方法的使用和上一个一样,传入 自定义的并发队列(DISPATCH_QUEUE_CONCURRENT
),它和上一个方法一样的阻塞 queue,不同的是 这个方法还会 阻塞当前线程。如果你传入的是其他的 queue, 那么它就和dispatch_sync
一样了。
NSOperation和NSOperationQueue
NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解:
- 将要执行的任务封装到一个
NSOperation
对象中。 - 将此任务添加到一个
NSOperationQueue
对象中。
然后系统就会自动在执行任务。植物同步异步、串行并行继续往下看:
添加任务
值得说的是,NSOperation
只是一个抽象类,所以不能封装任务。但他有两个子类用于封装任务。分别是:NSInvocationOperation
和NSBlockOperation
。创建一个Operation后,需要调用start
方法来启动任务,他会默认在当前队列同步执行。当然,也可以在钟乳取消任务,只需要调用其cancel
方法即可。
- NSInvocationOperation:需要传入一个方法名。
OBJECTIVE-C
//1.创建NSInvocationOperation对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.开始执行
[operation start];
- NSBlockOperation
OBJECTIVE-C
//1.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.开始任务
[operation start];
SWIFT
//1.创建NSBlockOperation对象
let operation = NSBlockOperation { () -> Void in
println(NSThread.currentThread())
}
//2.开始任务
operation.start()
之前说这样的任务会默认在当前线程执行。但是```NSBlockOperation ```还有一个方法:```addExecutionBlock ```,通过这个方法可以给Operation添加多个执行Block。这样Operation中的任务会并发执行,他会在主线程和其他的多个线程执行这些任务。
并且,addExecutionBlock
方法必须在start
方法前调用,否则就会报错。
- 自定义Operation
除了上面的两种Operation以外,我们还可以自定义Operation。自定义Operation需要继承NSOperation类。
创建队列
看过上面的内容就知道,我们可以调用一个NSOperation
对象的start()
方法来启动这个任务,但是这样做他们默认是 同步执行 的。就算是addExecutionBlock
方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。这是就要用到队列NSOperationQueue
了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法
- 主队列
每套多线程方案都会有一个主线程。这是一个特殊的线程,必须串行。所以添加到主队列的任务都会一个接一个的拍着队在主线程处理。 - 其他队列
因为主队列比较特殊,所以会单独有一个类方法来获得主队列。那么通过初始化产生的队列就是其他队列了,因为只有这两种队列,除了主队列,其他队列就不需要名字了。
注意:其他队列的任务会在其他线程并行执行。
将NSOperationQueue
与GCD
的队列 相比较就会发现,这里没有串行队列,那如果我想要10个任务在其他线程串行的执行怎么办?
这就是苹果封装的妙处,你不用管串行、并行、同步、异步这些名词。NSOperationQueue
有一个参数 maxConcurrentOperationCount
最大并发数,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,他不就是串行了嘛!
NSOperationQueue
还有一个添加任务的方法,- (void)addOperationWithBlock:(void (^)(void))block;
,这是不是和 GCD 差不多?这样就可以添加一个任务到队列中了,十分方便。
NSOperation
有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:
OBJECTIVE-C
//1.任务一:下载图片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载图片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//2.任务二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//3.任务三:上传图片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上传图片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//4.设置依赖
[operation2 addDependency:operation1]; //任务二依赖任务一
[operation3 addDependency:operation2]; //任务三依赖任务二
//5.创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
SWIFT
//1.任务一:下载图片
let operation1 = NSBlockOperation { () -> Void in
NSLog("下载图片 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//2.任务二:打水印
let operation2 = NSBlockOperation { () -> Void in
NSLog("打水印 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//3.任务三:上传图片
let operation3 = NSBlockOperation { () -> Void in
NSLog("上传图片 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//4.设置依赖
operation2.addDependency(operation1) //任务二依赖任务一
operation3.addDependency(operation2) //任务三依赖任务二
//5.创建队列并加入任务
let queue = NSOperationQueue()
queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)
注意:
- 不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。
- 可以使用
removeDependency
来解除依赖关系。 - 可以在不同的队列之间依赖,反正就是这个依赖是添加到任务身上的,和队列没关系。
其他方法
以上是主要方法,下面还有一些常用方法:
- 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)
- 同步执行:把多个线程都要执行此段代码添加到同一个串行队列,这样就实现了线程同步。
OBJECTIVE-C
//GCD
//需要一个全局变量queue,要让所有线程的这个操作都加到一个queue中
dispatch_sync(queue, ^{
NSInteger ticket = lastTicket;
[NSThread sleepForTimeInterval:0.1];
NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
ticket -= 1;
lastTicket = ticket;
});
//NSOperation & NSOperationQueue
//重点:1. 全局的 NSOperationQueue, 所有的操作添加到同一个queue中
// 2. 设置 queue 的 maxConcurrentOperationCount 为 1
// 3. 如果后续操作需要Block中的结果,就需要调用每个操作的waitUntilFinished,阻塞当前线程,一直等到当前操作完成,才允许执行后面的。waitUntilFinished 要在添加到队列之后!
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSInteger ticket = lastTicket;
[NSThread sleepForTimeInterval:1];
NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
ticket -= 1;
lastTicket = ticket;
}];
[queue addOperation:operation];
[operation waitUntilFinished];
//后续要做的事
延迟执行
所谓延迟执行就是延迟一段时间再执行某段代码。
- perform
// 3秒后自动调用self的run:方法,并且传递参数:@"abc"
[self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
- GCD
可以使用GCD中的dispatch_after
方法。
// 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 设置延时,单位秒
double delay = 3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
// 3秒后需要执行的任务
});
- NSTimer
NSTimer 是iOS中的一个计时器类,除了延迟执行还有很多用法,不过这里直说延迟执行的用法
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];
单例模式
OBJECTIVE-C
@interface Tool : NSObject
+ (instancetype)sharedTool;
@end
@implementation Tool
static id _instance;
+ (instancetype)sharedTool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[Tool alloc] init];
});
return _instance;
}
@end
SWIFT
class Tool: NSObject {
static let sharedTool = Tool()
// 私有化构造方法,阻止其他对象使用这个类的默认的'()'构造方法
private override init() {}
}
从其他线程回到主线程的方法
在其他线程操作完成后必须到主线程更新UI。
- NSThread
//Objective-C
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
//Swift
//swift 取消了 performSelector 方法。
- GCD
//Objective-C
dispatch_async(dispatch_get_main_queue(), ^{
});
//Swift
dispatch_async(dispatch_get_main_queue(), { () -> Void in
})
- NSOperationQueue
//Objective-C
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
//Swift
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in
}