进程与线程
进程:一个正在执行的程序实例,是 CPU 调度的基本单位。CUP 通过时间片轮转等调度算法在多个进程之间快速切换,制造多个进程并发的假象,从而实现多任务。但当遇到 I/O 操作阻塞时就会放弃进程时间片。这对性能有很大的影响,因为进程上下文切换开销很大。所以就有了线程来优化这种劣势。
线程:现在操作系统的中的轻量进程,是 CPU 调度的基本单位。而进程作为线程的容器,是资源管理的单位。线程的优势是当是某个线程 I/O 操作阻塞时,还可以执行其他线程从而最大化利用进程的时间片。
因为每个进程都有独立且保护的内存空间,保存文件、子进程、即将发生的报警、信号处理程序、账号信息等。线程只拥有程序计数器 、寄存器、堆栈等少量资源,但与其他线程共享整个进程的内存空间,因此线程切换速度要比进程快 10 到 100 倍。
为什么使用多线程
使用多线程可充分利用现在的多核 CPU、减少 CPU 的等待时间、防止主线程阻塞等。除了性能上的提升,对批量业务,使用多线程也能使代码逻辑更加清晰。
NSThread
苹果封装的线程管理对象, 不过 NSThread 创建时不代表一个真正的线程被创建,只有我们调用 start 时才真正创建了线程。
创建
- (instancetype)init API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;
使用该方法虽然是实例的创建,但确实不能创建一个线程,因为没有提供其他 api 来操作,没办法启动线程的 RunLoop。所以用这个函数创建线程是没有意思的。
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
可以使用这两种函数来创建线程的实例,这两个函数分别通过 selector 和 block 来管理线程后面执行任务,创建后使用 start 启动线程。
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
也可以使用这两种函数创建线程实例,和上面的区别是上面创建后要手动调用 start 来启动线程,而这两种构造方法则不用手动启动,他会在创建完后自动调用 start 来启动线程。
@property (class, readonly, strong) NSThread *currentThread;
@property (class, readonly, strong) NSThread *mainThread API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
当然,还可以使用这个两个函数获取当前线程和主线程。这也是 Apple 提供的获取线程对象的唯一两个函数。
启动
- (void)start API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)main API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
上面提到过启动线程使用 start 函数。main 函数是线程入口的主函数,子类重写后不用调用 super,Apple 建议不用去使用它。
派发任务
系统有提供分类的 API 对派发任务的函数。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
向主线程派发任务,值得注意的是他的带的几个参数。
arg:执行 slector 携带的参数,没有可以为 nil;
wait:布尔值,是否阻塞当前线程等待选择器,设置为 YES 时,等到当前线程执行完后,执行主线程 selector,设置为 NO 时,阻塞当前线程,先执行主线程 selector。如果当前为主线程,则立即执行 selector。
array:selector 的执行模式RunLoop model,要注意改值设置为 nil 或者空数组时,该方法返回时不执行指定的选择器。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
向指定线程派发任务。
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
向后台模式的线程派发任务。
优先级
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
两个函数分别设置优先级和获取优先级,优先级为 0.0-1.0 ,越大表示优先级越高,默认为0.5,但由于优先级由内核决定,也不保证实际一直是 0.5.
休眠
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
两个函数设置线程休眠,一个为休眠到某个时间,一个为休眠多长时间,时间段类型为 NSTimeInterval。
状态
@property (readonly, getter=isExecuting) BOOL executing API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@property (readonly, getter=isFinished) BOOL finished API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@property (readonly, getter=isCancelled) BOOL cancelled API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
布尔类型,分别线程是否正在进行、是否完成、是否取消。
退出
+ (void)exit;
- (void)cancel API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
一个类方法,一个实例方法;
exit:是使用currentThread类方法来访问当前线程。在退出线程之前,此方法将发送NSThreadWillExitNotification,并将线程退出到默认的通知中心。因为通知是同步发送的,所以确保NSThreadWillExitNotification的所有观察者在线程退出之前接收通知。
Apple 建议我们应该避免调用此方法,因为它没有给您的线程一个机会来清理它在执行期间分配的任何资源。
cancel:这个方法和 NSOperation 的 cancle 方法类似,正常取消线程,释放资源。
GCD
苹果自己封装的一套管理现成的 API,分串行队列和并发队列,任务派发分异步任务和同步任务,两者可组合搭配。队列原则是先进先出,所以不管是串行队列还是并发队列都是按顺序执行的。串行队列需要在前一个任务执行完后执行下一个任务而并发队列可以允许多个任务同时进行,虽然他们开始的时间是一致的,但结束的时间却不确定。
队列和派发任务搭配结果:
同步派发 | 异步派发 | |
---|---|---|
串行队列 | 当前线程串行执行,阻塞当前线程 | 新建单个线程串行执行,不阻塞当钱线程 |
并发队列 | 当前线程并发执行,阻塞当前线程 | 新建多个线程并发执行,不阻塞当前线程 |
主队列(串行队列) | 主线程串行执行,阻塞当前线程 | 主线程串行执行,不阻塞当前线程 |
这里要说一点,就是串行队列搭配同步派发任务后,在添加任务后,该任务被添加在队列的末尾,要执行这个任务就得前面任务等待执行完,而刚派发的这个同步任务要阻塞线程直到自己执行完,然后造成死锁。
全局并发队列
dispatch_queue_t
dispatch_get_global_queue(long identifier, unsigned long flags);
全局串行主队列
dispatch_queue_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}
自定义队列
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
label 队列标签,以便调试时标注,一般为反dns,attr 设置不同,可以创建串行队列或者并发队列。下面分别对应不同参数
- DISPATCH_QUEUE_SERIAL:创建的队列为串行队列;
- DISPATCH_QUEUE_CONCURRENT:创建的队列为并发队列。
异步派发任务
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
同步派发任务
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
NSOperation、NSOperationQueue
NSOperation 也是一个任务面向对象的封装,不过他是一个抽象类,在使用它的使用应该是用他的子类 NSBlockOperation 和 NSInvocationOperation ,这两个类分别以 Blok 和 Invocation 方式来管理任务。然后搭配 NSOperationQueue 面向对象队列的封装来使用,更好管理多线程。
NSBlockOperation
NSBlockOperation 通过 Block 来管理任务,可以使用 init 创建,然后通过 addExecutionBlock 来添加多个任务,也可以使用 blockOperationWithBlock 创建且添加任务,然后调用 start 开始执行。
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
NSInvocationOperation
NSInvocationOperation 通过 Invocation 管理任务,通过 taeget ,selector 形式创建, 然后 start 开始执行。
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
NSOperationQueue
NSOperationQueue 队列的面向对象封装,可以和 NSOperation 子类更好的管理线程。
默认的最大并发数,由当前系统决定
static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1;
添加队列管理,参数 wait 为布尔类型,YES 时同步执行,阻塞当前线程,NO 时异步执行,不阻塞当前线程。
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- (void)addOperationWithBlock:(void (^)(void))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
获取并发队列数,1 时代表此时队列为串行队列
@property NSInteger maxConcurrentOperationCount;
getter 方法为获取队列状态,setter 方法为设置队列状态(暂停 or 恢复)
@property (getter=isSuspended) BOOL suspended;
取消所有队列
- (void)cancelAllOperations;
阻塞当前线程直到所有任务完成,同步执行
- (void)waitUntilAllOperationsAreFinished;
获取主队列、当前队列
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
@property (class, readonly, strong) NSOperationQueue *mainQueue API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));