总览
iOS多线程开发的技术,主要包括pthread、NSThread、GCD、NSOperation;其中pthread/NSThread用的情况不多,而GCD和NSOperation用的比较频繁,所以是重点。
pthread
pthread的全称叫POSIX Thread,是一个线程管理的通用规范,基本上所有的开发平台都有实现,所以用pthread实现的代码,其通用性比较强。然而pthread不是面向对象的,而且需要开发者自己管理线程,包括创建线程、结束线程、处理冲突、各种锁等,一般在底层开发的时候用的会比较多,比如C语言的开发等,在iOS中用的很少。我曾经在一个音频库中用过。这个东西了解一下就好。
NSThread
NSThread可以说是对pthread的封装,里面有几个比较好用的方法,下面一一讲一下。
初始化和开始线程
//初始化线程,SEL是线程的入口
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;
//开始线程
- (void)start;
暂停线程
//让线程休眠到某一时刻
+ (void)sleepUntilDate:(NSDate *)date;
//让线程休眠一段时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
几个变量+方法,获取线程的情况
NSThread有几个参数,可以很方便的得到线程的运行情况:
executing
:线程是否在运行;
finished
:线程是否结束;
cancelled
:线程是否被取消了;
[NSThread isMainThread]
:当前线程是不是主线程;
[NSThread currentThread]
:获取当前线程;
可以看出来,NSThread的功能比较简单,实际开发中用的也不多,了解一下就好。
GCD(Grand Central Dispatch)
同学们注意,划重点了!(敲黑板)
简介
GCD是iOS和Mac系统中实现的多线程模块,在实际开发过程中用的也最多,要着重掌握。
几个概念:
queue:也就是dispatch_queue,就是处理多线程的queue,tasks都是放在queue中处理的。一个queue可以维护多个threads。
task:每一个任务,叫做task;task被enqueue进queue之后,由queue自主分配线程执行。
GCD的优点:
1、使用简单,不需要创建和维护线程;
2、灵活方便,大部分情况下可以避免维护线程锁;
下面逐个知识点介绍。
dispatch_queue的种类
共2类:
serial queue:也就是每次只能执行一个task,后面再enqueue进来的task,要等到前面的task执行完之后再依次执行,即FIFO规则。serial queue的这个特性,可以用来处理多个线程要写一个资源的情况。将所有tasks放到一个serial queue中,就可以直接实现这个需求,而不需要自己管理锁;
concurrent queue:可以同步执行多个tasks。每个task运行于独立的线程中。执行任务时,也是按照添加到queue的顺序执行。
一个特殊的queue:main queue,也就是刷新UI所用的queue。它是serial queue,所以当阻塞main queue的时候,UI的刷新就会被阻塞。main queue通过dispatch_get_main_queue()
方法得到。
系统提供了4个可以直接使用的concurrent queues,按照queue的优先级,分为4种:
high/default/low/background;
通过dispatch_get_global_queue()
方法来获取。
dispatch执行task的方式
queue执行task的方式有2种:async和sync方式。
async方法:用这种方法执行task时,当前queue(一般是main queue)不会被阻塞。async方法比较更常用一些。
sync方法:用这种方法执行task时,当前queue会等待task任务完成,再继续执行下面的任务。比如,如果在main queue时,使用sync方法,main queue就会被阻塞,此时UI无法操作。
注意:如果在一个serial queue中执行sync方法,并再次调用此queue,会导致死锁。下面举个栗子:
//在main queue中
dispatch_sync(dispatch_get_main_queue(),^{
NSLog(@"where is main queue?");
});
为什么会导致死锁?因为main queue是一个serial queue,此时执行sync方法,会导致main queue被锁住。而sync方法又要求在main queue中执行task(此时main queue已经被锁住了),导致此task一直在等待解锁,从而导致死锁。
dispatch_group
dispatch_group是用来管理一组queue的方法。比如,如果需要在一组queue都执行完任务再进行下一步操作,就可以用到dispatch_group。
具体做法:
//将queue加入到group
dispatch_group_async(group, queue, ^(block));
//多个dispatch_group_async
//...
//当所有block都执行完毕,此block才会被调用
dispatch_group_notify(group, queue, ^(block));
//或者用wait方法也可以实现,还可以加一个超时时间
dispatch_group_wait(group, 10);//阻塞当前线程
//继续操作
//...
其它要注意的地方
dispatch_apply使用技巧
根据苹果的官方文档,如果需要很多个任务,可以用dispatch_apply()来实现,这样做比直接用for循环效率高。
dispatch_apply是sync的调用方法,因此会阻塞当前线程。可以将dispatch_apply包装到dispatch_async中来实现。按照文档说法,调用dispatch_apply后,需要等待所有tasks都执行结束,才会执行后面的操作。所以它也应该可以实现dispatch_group可以实现的功能。
dispatch_get_current_queue被deprecated了
为什么?我的理解是,有2个原因:
- 如果在queue A中调用queue B,这个时候current queue是A还是B呢?这个就比较难以回答。一般来讲我们想得到的答案是A,但实际上结果是B。但我们没必要知道我们在B上,因为是我们自己调用的queue B。
- 如果当前queue是serial queue,容易导致死锁。
单例模式的几种实现方式
-
用
@synchronized
关键字:static id instance;
- (instancetype)singleton{
@synchronized(self){
if (instance == nil){
instance = ...;
}
}
return instance;
}@synchronized是Objective-C层面实现的互斥锁,以此保证同一时刻只会执行一个task,从而实现单例模式。
-
用
dispatch_once
:static id instance;
- (instancetype)singleton{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = ...;
});
return instance;
}dispatch_once可以保证代码只被执行一次,从而实现单例模式。
如何解决争夺资源的问题?
- 用@synchronized方法;
- 用serial queue;
- 用dispatch_barrier_async()方法。此方法会保证queue在当前时刻,只会执行这一个task;
NSOperaion/NSOperationQueue
NSOperation和NSOperationQueue是对GCD的封装,使之面向对象,更好用。
NSOperation -> GCD的task;
NSOperationQueue -> GCD的queue;
NSOperation有2个subClass,NSInvocationOperation(调用function)和NSBlockOperation(调用block)。
讲一下它们的优势。
NSOperation在执行的过程中,可以方便的取消操作:
[operation cancel];
还可以设置任务执行完成之后的操作:
[operation setCompletionBlock:^{
//任务完成后的操作
}];
NSOperation可以添加多个操作,这些操作是在多个线程中同时执行的:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//操作1
}];
[operation addExecutionBlock:^{
//操作2
}];
[operation addExecutionBlock:^{
//操作3
}];
[operation start];
NSOperation可以同时管理多个operation,比如批量暂停、等待所有操作都完成之后再继续等。
NSOperationQueue可以设置队列的最大并发操作数目:
queue.maxConcurrentOperationCount = 1;
这样就实现了一个serial queue。实现concurrent queue也是一样,只是将数目改一下就好了。
NSOperation有一个比较强大的功能,可以设置各个operation之间的相互依赖:
//在operationA完成之后,operationB才开始执行
[operationB addDependency:operationA];
而且支持图状的依赖(当然是无环图,如果是有环图的话,就死锁了)。