iOS多线程实践中,经常使用的就是子线程执行耗时操做,而后回到主线程刷新UI。在iOS中每一个进程启动后都会创建一个主线程(UI线程),这个线程是其余线程的父线程。因为在iOS中除了主线程,其余子线程是独立于Cocoa Touch的,因此只有主线程能够更新UI界面。iOS多线程开发实践方式有4种,分别为Pthreads、NSThread、GCD、NSOperation,下面分别讲一讲各自的使用方式,以及优缺点。node
pthread: 跨平台,适用于多种操做系统,可移植性强,是一套纯C语言的通用API,且线程的生命周期须要程序员本身管理,使用难度较大,因此在实际开发中一般不使用。 NSThread: 基于OC语言的API,使得其简单易用,面向对象操做。线程的声明周期由程序员管理,在实际开发中偶尔使用。 GCD: 基于C语言的API,充分利用设备的多核,旨在替换NSThread等线程技术。线程的生命周期由系统自动管理,在实际开发中常用。 NSOperation: 基于OC语言API,底层是GCD,增长了一些更加简单易用的功能,使用更加面向对象。线程生命周期由系统自动管理,在实际开发中常用。程序员
引自 维基百科 实现POSIX 线程标准的库常被称做Pthreads,通常用于Unix-like POSIX 系统,如Linux、 Solaris。可是Microsoft Windows上的实现也存在,例如直接使用Windows API实现的第三方库pthreads-w32;而利用Windows的SFU/SUA子系统,则可使用微软提供的一部分原生POSIX API。api
其实,这就是一套在不少操做系统上都通用的多线程API,因此移植性很强,基于C封装的一套线程框架,iOS上也是适用的。数组
- (void)onThread {
// 1. 建立线程: 定义一个pthread_t类型变量
pthread_t thread;
// 2. 开启线程: 执行任务
pthread_create(&thread, NULL, run, NULL);
// 3. 设置子线程的状态设置为detached,该线程运行结束后会自动释放全部资源
pthread_detach(thread);
}
void * run(void *param) {
NSLog(@"%@", [NSThread currentThread]);
return NULL;
}
复制代码
打印结果: 2018-03-16 11:06:12.298115+0800 ocgcd[13744:5710531] <NSThread: 0x1c026f100>{number = 4, name = (null)}安全
若是出现'pthread_create' is invalid in C99
报错,缘由是没有导入#import <pthread.h>
bash
——pthread_create(&thread, NULL, run, NULL);
中各项参数含义:——微信
&thread
是线程对象,指向线程标识符的指针NULL
run
表示指向函数的指针(run
对应函数里是须要在新线程中执行的任务)NULL
pthread_create()
:建立一个线程pthread_exit()
:终止当前线程pthread_cancel()
:中断另一个线程的运行pthread_join()
:阻塞当前的线程,直到另一个线程运行结束pthread_attr_init()
:初始化线程的属性pthread_attr_setdetachstate()
:设置脱离状态的属性(决定这个线程在终止时是否能够被结合)pthread_attr_getdetachstate()
:获取脱离状态的属性pthread_attr_destroy()
:删除线程的属性pthread_kill()
:向线程发送一个信号pthread_t用于表示Thread ID,具体内容根据实现的不一样而不一样,有多是一个Structure,所以不能将其看做为整数。多线程
pthread_equal函数用于比较两个pthread_t是否相等。并发
int pthread_equal(pthread_t tid1, pthread_t tid2)
复制代码
pthread_self函数用于得到本线程的thread id。app
pthread _t pthread_self(void);
复制代码
锁能够被动态或静态建立,能够用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是
pthread_mutex_t
的结构体,而这个宏是一个结构常量,以下能够完成静态的初始化锁:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
也能够用pthread_mutex_init函数动态的建立,函数原型如:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)
总共有100张火车票,开启两个线程,北京和上海两个窗口同时卖票,卖一张票就减去库存,使用锁,保证北京和上海卖票的库存是一致的。实现以下。
#import "ViewController.h"
#include <pthread.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self onThread];
}
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
NSMutableArray *tickets;
- (void)onThread {
tickets = [NSMutableArray array];
//生成100张票
for (int i = 0; i < 100; i++) {
[tickets addObject:[NSNumber numberWithInt:i]];
}
//线程1 北京卖票窗口
// 1. 建立线程1: 定义一个pthread_t类型变量
pthread_t thread1;
// 2. 开启线程1: 执行任务
pthread_create(&thread1, NULL, run, NULL);
// 3. 设置子线程1的状态设置为detached,该线程运行结束后会自动释放全部资源
pthread_detach(thread1);
//线程2 上海卖票窗口
// 1. 建立线程2: 定义一个pthread_t类型变量
pthread_t thread2;
// 2. 开启线程2: 执行任务
pthread_create(&thread2, NULL, run, NULL);
// 3. 设置子线程2的状态设置为detached,该线程运行结束后会自动释放全部资源
pthread_detach(thread2);
}
void * run(void *param) {
while (true) {
//锁门,执行任务
pthread_mutex_lock(&mutex);
if (tickets.count > 0) {
NSLog(@"剩余票数%ld, 卖票窗口%@", tickets.count, [NSThread currentThread]);
[tickets removeLastObject];
[NSThread sleepForTimeInterval:0.2];
}
else {
NSLog(@"票已经卖完了");
//开门,让其余任务能够执行
pthread_mutex_unlock(&mutex);
break;
}
//开门,让其余任务能够执行
pthread_mutex_unlock(&mutex);
}
return NULL;
}
@end
复制代码
打印结果: 2018-03-16 11:47:01.069412+0800 ocgcd[13758:5723862] 剩余票数100, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:01.272654+0800 ocgcd[13758:5723863] 剩余票数99, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} 2018-03-16 11:47:01.488456+0800 ocgcd[13758:5723862] 剩余票数98, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:01.691334+0800 ocgcd[13758:5723863] 剩余票数97, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} ................. 2018-03-16 11:47:12.110962+0800 ocgcd[13758:5723862] 剩余票数46, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:12.316060+0800 ocgcd[13758:5723863] 剩余票数45, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} 2018-03-16 11:47:12.529002+0800 ocgcd[13758:5723862] 剩余票数44, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:12.731459+0800 ocgcd[13758:5723863] 剩余票数43, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} ................. 2018-03-16 11:47:21.103237+0800 ocgcd[13758:5723862] 剩余票数2, 卖票窗口<NSThread: 0x1c0667600>{number = 5, name = (null)} 2018-03-16 11:47:21.308605+0800 ocgcd[13758:5723863] 剩余票数1, 卖票窗口<NSThread: 0x1c0672b40>{number = 6, name = (null)} 2018-03-16 11:47:21.511062+0800 ocgcd[13758:5723862] 票已经卖完了 2018-03-16 11:47:21.511505+0800 ocgcd[13758:5723863] 票已经卖完了
对锁的操做主要包括加锁pthread_mutex_lock()
、解锁pthread_mutex_unlock()
和测试加锁pthread_mutex_trylock()
三个。 pthread_mutex_trylock()
语义与pthread_mutex_lock()
相似,不一样的是在锁已经被占据时返回EBUSY
而不是挂起等待。
NSThread
是面向对象的,封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大。NSThread
的基本使用比较简单,能够动态建立初始化NSThread
对象,对其进行设置而后启动;也能够经过NSThread
的静态方法快速建立并启动新线程;此外NSObject
基类对象还提供了隐式快速建立NSThread
线程的performSelector
系列类别扩展工具方法;NSThread
还提供了一些静态工具接口来控制当前线程以及获取当前线程的一些信息。
NSThread有三种建立方式:
initWithTarget
方式,先建立线程对象,再启动- (void)onThread {
// 建立并启动
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 设置线程名
[thread setName:@"thread1"];
// 设置优先级,优先级从0到1,1最高
[thread setThreadPriority:0.9];
// 启动
[thread start];
}
- (void)run {
NSLog(@"当前线程%@", [NSThread currentThread]);
}
复制代码
打印结果: 2018-03-16 13:47:25.133244+0800 ocgcd[13811:5776836] 当前线程<NSThread: 0x1c0264480>{number = 4, name = thread1}
- (void)onThread {
// 使用类方法建立线程并自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
- (void)run {
NSLog(@"当前线程%@", [NSThread currentThread]);
}
复制代码
打印结果: 2018-03-16 13:49:34.620546+0800 ocgcd[13814:5777803] 当前线程<NSThread: 0x1c026a940>{number = 5, name = (null)}
- (void)onThread {
// 使用NSObject的方法隐式建立并自动启动
[self performSelectorInBackground:@selector(run) withObject:nil];
}
- (void)run {
NSLog(@"当前线程%@", [NSThread currentThread]);
}
复制代码
打印结果: 2018-03-16 13:54:33.451895+0800 ocgcd[13820:5780922] 当前线程<NSThread: 0x1c4460280>{number = 4, name = (null)}
//获取当前线程
+(NSThread *)currentThread;
//建立线程后自动启动线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
//是不是多线程
+ (BOOL)isMultiThreaded;
//线程字典
- (NSMutableDictionary *)threadDictionary;
//线程休眠到什么时间
+ (void)sleepUntilDate:(NSDate *)date;
//线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//退出线程
+ (void)exit;
//线程优先级
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
- (double)threadPriority NS_AVAILABLE(10_6, 4_0);
- (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
//调用栈返回地址
+ (NSArray *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0);
+ (NSArray *)callStackSymbols NS_AVAILABLE(10_6, 4_0);
//设置线程名字
- (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0);
- (NSString *)name NS_AVAILABLE(10_5, 2_0);
//获取栈的大小
- (NSUInteger)stackSize NS_AVAILABLE(10_5, 2_0);
- (void)setStackSize:(NSUInteger)s NS_AVAILABLE(10_5, 2_0);
// 得到主线程
+ (NSThread *)mainThread;
//是不是主线程
- (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0);
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
//初始化方法
- (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0);
//是否正在执行
- (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);
//是否执行完成
- (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);
//是否取消线程
- (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//线程启动
- (void)start NS_AVAILABLE(10_5, 2_0);
- (void)main NS_AVAILABLE(10_5, 2_0); // thread body method
@end
//多线程通知
FOUNDATION_EXPORT NSString * const NSWillBecomeMultiThreadedNotification;
FOUNDATION_EXPORT NSString * const NSDidBecomeSingleThreadedNotification;
FOUNDATION_EXPORT NSString * const NSThreadWillExitNotification;
@interface NSObject (NSThreadPerformAdditions)
//与主线程通讯
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
//与其余子线程通讯
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// equivalent to the first method with kCFRunLoopCommonModes
//隐式建立并启动线程
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg NS_AVAILABLE(10_5, 2_0);
复制代码
// 线程启动
- (void)start;
复制代码
// 线程休眠到某一时刻
+ (void)sleepUntilDate:(NSDate *)date;
// 线程休眠多久
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
复制代码
// 结束线程
+ (void)exit;
复制代码
关于cancel
的疑问,当使用cancel
方法时,只是改变了线程的状态标识,并不能结束线程,因此咱们要配合isCancelled
方法进行使用。
- (void)onThread {
// 使用NSObject的方法隐式建立并自动启动
[self performSelectorInBackground:@selector(run) withObject:nil];
}
- (void)run {
NSLog(@"当前线程%@", [NSThread currentThread]);
for (int i = 0 ; i < 100; i++) {
if (i == 20) {
//取消线程
[[NSThread currentThread] cancel];
NSLog(@"取消线程%@", [NSThread currentThread]);
}
if ([[NSThread currentThread] isCancelled]) {
NSLog(@"结束线程%@", [NSThread currentThread]);
//结束线程
[NSThread exit];
NSLog(@"这行代码不会打印的");
}
}
}
复制代码
打印结果: 2018-03-16 14:11:44.423324+0800 ocgcd[13833:5787076] 当前线程<NSThread: 0x1c4466840>{number = 4, name = (null)} 2018-03-16 14:11:44.425124+0800 ocgcd[13833:5787076] 取消线程<NSThread: 0x1c4466840>{number = 4, name = (null)} 2018-03-16 14:11:44.426391+0800 ocgcd[13833:5787076] 结束线程<NSThread: 0x1c4466840>{number = 4, name = (null)}
线程的状态以下图:
一、新建:实例化对象
二、就绪:向线程对象发送start
消息,线程对象被加入“可调度线程池”等待CPU
调度;detach
方法和performSelectorInBackground
方法会直接实例化一个线程对象并加入“可调度线程池”
三、运行:CPU
负责调度“可调度线程池”中线程的执行,线程执行完成以前,状态可能会在“就绪”和“运行”之间来回切换,“就绪”和“运行”之间的状态变化由CPU
负责,程序员不能干预
四、阻塞:当知足某个预约条件时,可使用休眠或锁阻塞线程执行,影响的方法有:sleepForTimeInterval
,sleepUntilDate
,@synchronized(self)
线程锁;线程对象进入阻塞状态后,会被从“可调度线程池”中移出,CPU 再也不调度
五、死亡
死亡方式:
正常死亡:线程执行完毕 非正常死亡:线程内死亡--->[NSThread exit]
:强行停止后,后续代码都不会在执行 线程外死亡:[threadObj cancel]
--->通知线程对象取消,在线程执行方法中须要增长isCancelled
判断,若是isCancelled == YES
,直接返回
死亡后线程对象的isFinished
属性为YES
;若是是发送cancle
消息,线程对象的isCancelled
属性为YES
;死亡后stackSize == 0
,内存空间被释放
在开发中,咱们常常会在子线程进行耗时操做,操做结束后再回到主线程去刷新UI。这就涉及到了子线程和主线程之间的通讯。看一下官方关于NSThread的线程间通讯的方法。
// 在主线程上执行操做
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;
// equivalent to the first method with kCFRunLoopCommonModes
// 在指定线程上执行操做
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// 在当前线程上执行操做,调用 NSObject 的 performSelector:相关方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
复制代码
下面经过一个经典的下载图片DEMO来展现线程之间的通讯。具体步骤以下: 一、开启一个子线程,在子线程中下载图片。 二、回到主线程刷新UI,将图片展现在UIImageView
中。
func onThread() {
let urlStr = "http://tupian.aladd.net/2015/7/2941.jpg"
self.performSelector(inBackground: #selector(downloadImg(_:)), with: urlStr)
}
@objc func downloadImg(_ urlStr: String) {
//打印当前线程
print("下载图片线程", Thread.current)
//获取图片连接
guard let url = URL.init(string: urlStr) else {return}
//下载图片二进制数据
guard let data = try? Data.init(contentsOf: url) else {return}
//设置图片
guard let img = UIImage.init(data: data) else {return}
//回到主线程刷新UI
self.performSelector(onMainThread: #selector(downloadFinished(_:)), with: img, waitUntilDone: false)
}
@objc func downloadFinished(_ img: UIImage) {
//打印当前线程
print("刷新UI线程", Thread.current)
// 赋值图片到imageview
self.imageView.image = image
}
复制代码
线程安全,也能够被称为线程同步,主要是解决多线程争抢操做资源的问题,就好比火车票,全国各地多个售票窗口同事去售卖同一列火车票。 怎么保证,多地售票的票池保持一致,就须要用到多线程同步的技术去实现了。
NSThread线程安全和GCD、NSOperation线程安全都是同样的,实现方法无非就是加锁(各类锁的实现)、信号量、GCD栅栏等。 具体实现,能够看iOS多线程详解:概念篇
线程同步段落。
GCD(Grand Central Dispatch是苹果为多核并行运算提出的C语言并发技术框架。 GCD会自动利用更多的CPU内核; 会自动管理线程的生命周期(建立线程,调度任务,销毁线程等); 程序员只须要告诉GCD想要如何执行什么任务,不须要编写任何线程管理代码。
咱们使用的GCD的API是C语言函数,所有包含在LIBdispatch库中,DispatchQueue经过结构体和链表被实现为FIFO的队列;而FIFO的队列是由dispatch_async等函数追加的Block来管理的;Block不是直接加入FIFO队列,而是先加入Dispatch Continuation结构体,而后在加入FIFO队列,Dispatch Continuation用于记忆Block所属的Dispatch Group和其余一些信息(至关于上下文)。 Dispatch Queue可经过dispatch_set_target_queue()设定,能够设定执行该Dispatch Queue处理的Dispatch Queue为目标。该目标可像串珠子同样,设定多个链接在一块儿的Dispatch Queue,可是在链接串的最后必须设定Main Dispatch Queue,或各类优先级的Global Dispatch Queue,或是准备用于Serial Dispatch Queue的Global Dispatch Queue
Global Dispatch Queue的8种优先级:
.High priority .Default Priority .Low Priority .Background Priority .High Overcommit Priority .Default Overcommit Priority .Low Overcommit Priority .Background Overcommit Priority
附有Overcommit的Global Dispatch Queue使用在Serial Dispatch Queue中,无论系统状态如何,都会强制生成线程的 Dispatch Queue。 这8种Global Dispatch Queue各使用1个pthread_workqueue
GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_workqueue。pthread_workqueue包含在Libc提供的pthreads的API中,他使用bsthread_register和workq_open系统调用,在初始化XNU内核的workqueue以后获取workqueue信息。
其中XNU有四种workqueue:
WORKQUEUE_HIGH_PRIOQUEUE WORKQUEUE_DEFAULT_PRIOQUEUE WORKQUEUE_LOW_PRIOQUEUE WORKQUEUE_BG_PRIOQUEUE
这四种workqueue与Global Dispatch Queue的执行优先级相同
一、当在Global Dispatch Queue中执行Block时,libdispatch从Global Dispatch Queue自身的FIFO中取出Dispatch Continuation,调用pthread_workqueue_additem_np函数,将该Global Dispatch Queue、符合其优先级的workqueue信息以及执行Dispatch Continuation的回调函数等传递给pthread_workqueue_additem_np函数的参数。
二、thread_workqueue_additem_np()使用workq_kernreturn系统调用,通知workqueue增长应当执行的项目。
三、根据该通知,XUN内核基于系统状态判断是否要生成线程,若是是Overcommit优先级的Global Dispatch Queue,workqueue则始终生成线程。
四、workqueue的线程执行pthread_workqueue(),该函数用libdispatch的回调函数,在回调函数中执行执行加入到Dispatch Continuatin的Block。
五、Block执行结束后,进行通知Dispatch Group结束,释放Dispatch Continuation等处理,开始准备执行加入到Dispatch Continuation中的下一个Block。
GCD 的使用步骤其实很简单,只有两步。
一、建立一个队列(串行队列或并发队列) 二、将任务追加到任务的等待队列中,而后系统就会根据任务类型执行任务(同步执行或异步执行)
iOS系统默认已经存在两种队列,主队列(串行队列)和全局队列(并发队列),那咱们能够利用GCD提供的接口建立并发和串行队列。
关于同步、异步、串行、并行的概念和区别,在iOS多线程详解:概念篇
中有详细说明
//建立串行队列
let que = DispatchQueue.init(label: "com.jacyshan.thread")
复制代码
使用DispatchQueue
初始化建立队列,默认是串行队列。第一个参数是表示队列的惟一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名。
//建立并发队列
let que = DispatchQueue.init(label: "com.jacyshan.thread", attributes: .concurrent)
复制代码
第二个参数输入.concurrent
标示建立的是一个并发队列
主队列(Main Dispatch Queue)是GCD 提供了的一种特殊的串行队列 全部放在主队列中的任务,都会放到主线程中执行。
//获取主队列
let que = DispatchQueue.main
复制代码
GCD 默认提供了全局并发队列(Global Dispatch Queue)。
//获取全局队列
let que = DispatchQueue.global()
复制代码
GCD 提供了同步执行任务的建立方法sync和异步执行任务建立方法async。
//同步执行任务建立方法
que.sync {
print("任务1", Thread.current)
}
//异步执行任务建立方法
que.async {
print("任务2", Thread.current)
}
复制代码
有两种队列(串行队列/并发队列),两种任务执行方式(同步执行/异步执行),那么咱们就有了四种不一样的组合方式。这四种不一样的组合方式是:
一、同步执行 + 并发队列 二、异步执行 + 并发队列 三、同步执行 + 串行队列 四、异步执行 + 串行队列
系统还提供了两种特殊队列:全局并发队列、主队列。全局并发队列能够做为普通并发队列来使用。可是主队列由于有点特殊,因此咱们就又多了两种组合方式。这样就有六种不一样的组合方式了。
五、同步执行 + 主队列 六、异步执行 + 主队列
六中组合方式区别经过显示以下。
区别 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步执行 | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 主线程调用:死锁卡住不执行 其余线程调用:没有开启新线程,串行执行任务 |
异步执行 | 有开启新线程,并发执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
在当前线程中执行任务,任务按顺序执行。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列
let que = DispatchQueue.init(label: "com.jackyshan.thread", attributes: .concurrent)
//添加任务1
que.sync {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务1Thread---", Thread.current) //打印当前线程
}
}
//添加任务2
que.sync {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务2Thread---", Thread.current)
}
}
print("代码块------end")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c0078f00>{number = 1, name = main} 代码块------begin 任务1Thread--- <NSThread: 0x1c0078f00>{number = 1, name = main} 任务1Thread--- <NSThread: 0x1c0078f00>{number = 1, name = main} 任务2Thread--- <NSThread: 0x1c0078f00>{number = 1, name = main} 任务2Thread--- <NSThread: 0x1c0078f00>{number = 1, name = main} 代码块------end
能够看到任务是在主线程执行的,由于同步并无开启新的线程。由于同步会阻塞线程,因此当咱们的任务操做耗时的时候,咱们界面的点击和滑动都是无效的。 由于UI的操做也是在主线程,可是任务的耗时已经阻塞了线程,UI操做是没有反应的。
开启多个线程,任务交替执行。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列
let que = DispatchQueue.init(label: "com.jackyshan.thread", attributes: .concurrent)
//添加任务1
que.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务1Thread---", Thread.current) //打印当前线程
}
}
//添加任务2
que.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务2Thread---", Thread.current)
}
}
print("代码块------end")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c0062180>{number = 1, name = main} 代码块------begin 代码块------end 任务1Thread--- <NSThread: 0x1c02695c0>{number = 4, name = (null)} 任务2Thread--- <NSThread: 0x1c02694c0>{number = 5, name = (null)} 任务1Thread--- <NSThread: 0x1c02695c0>{number = 4, name = (null)} 任务2Thread--- <NSThread: 0x1c02694c0>{number = 5, name = (null)}
能够看到任务是在多个新的线程执行完成的,并无在主线程执行,所以任务在执行耗时操做的时候,并不会影响UI操做。 异步能够开启新的线程,并发又能够执行多个线程的任务。由于异步没有阻塞线程,代码块------begin 代码块------end
当即执行了,其余线程执行完耗时操做以后才打印。
在当前线程中执行任务,任务按顺序执行。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立串行队列,DispatchQueue默认是串行队列
let que = DispatchQueue.init(label: "com.jackyshan.thread")
//添加任务1
que.sync {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务1Thread---", Thread.current) //打印当前线程
}
}
//添加任务2
que.sync {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务2Thread---", Thread.current)
}
}
print("代码块------end")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c40658c0>{number = 1, name = main} 代码块------begin 任务1Thread--- <NSThread: 0x1c40658c0>{number = 1, name = main} 任务1Thread--- <NSThread: 0x1c40658c0>{number = 1, name = main} 任务2Thread--- <NSThread: 0x1c40658c0>{number = 1, name = main} 任务2Thread--- <NSThread: 0x1c40658c0>{number = 1, name = main} 代码块------end
一样的,串行执行同步任务的时候,也没有开启新的线程,在主线程上执行任务,耗时操做会影响UI操做。
开启一个新的线程,在新的线程中执行任务,任务按顺序执行。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立串行队列,DispatchQueue默认是串行队列
let que = DispatchQueue.init(label: "com.jackyshan.thread")
//添加任务1
que.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务1Thread---", Thread.current) //打印当前线程
}
}
//添加任务2
que.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务2Thread---", Thread.current)
}
}
print("代码块------end")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c407b700>{number = 1, name = main} 代码块------begin 代码块------end 任务1Thread--- <NSThread: 0x1c0462440>{number = 4, name = (null)} 任务1Thread--- <NSThread: 0x1c0462440>{number = 4, name = (null)} 任务2Thread--- <NSThread: 0x1c0462440>{number = 4, name = (null)} 任务2Thread--- <NSThread: 0x1c0462440>{number = 4, name = (null)}
从打印能够看到只开启了一个线程(串行只会开启一个线程),任务是在新的线程按顺序执行的。 任务是在代码块------begin 代码块------end
后执行的(异步不会等待任务执行完毕)
下面是讲__主队列__,主队列是一种特殊的串行队列,全部任务(异步同步)都会在主线程执行。
任务在主线程中调用会出现死锁,其余线程不会。
同步+主队列
界面卡死,全部操做没有反应。任务互相等待形成死锁。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//获取主队列
let que = DispatchQueue.main
//添加任务1
que.sync {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务1Thread---", Thread.current) //打印当前线程
}
}
//添加任务2
que.sync {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务2Thread---", Thread.current)
}
}
print("代码块------end")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c00766c0>{number = 1, name = main} 代码块------begin
能够看到代码块------begin
执行完,后面就不执行的,卡住不动了,等一会还会崩溃。
感受死锁不少文章讲的不是很清楚,其实流程就是互相等待,简单解释以下:
缘由是onThread()
这个任务是在主线程执行的,任务1
被添加到主队列,要等待队列onThread()
任务执行完才会执行。 而后,任务1
是在onThread()
这个任务中的,按照FIFO
的原则,onThread()
先被添加到主队列,应该先执行完,可是任务1
在等待onThread()
执行完才会执行。 这样就形成了死锁,互相等待对方完成任务。
同步+主队列
主队列不会开启新的线程,任务按顺序在主线程执行
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//开启新的线程执行onThread任务
performSelector(inBackground: #selector(onThread), with: nil)
}
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//获取主队列
let que = DispatchQueue.main
//添加任务1
que.sync {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务1Thread---", Thread.current) //打印当前线程
}
}
//添加任务2
que.sync {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务2Thread---", Thread.current)
}
}
print("代码块------end")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c0076a80>{number = 4, name = (null)} 代码块------begin 任务1Thread--- <NSThread: 0x1c406d680>{number = 1, name = main} 任务1Thread--- <NSThread: 0x1c406d680>{number = 1, name = main} 任务2Thread--- <NSThread: 0x1c406d680>{number = 1, name = main} 任务2Thread--- <NSThread: 0x1c406d680>{number = 1, name = main} 代码块------end
onThread
任务是在其余线程执行的,没有添加到主队列,全部也不会等待任务一、2
的完成,所以不会死锁。
这里有个疑问,有的人会想串行队列+同步
和并发队列+同步
为何不会死锁呢,其实若是onThread
任务和同步任务
在同一个队列中,并且同步任务
是在onThread
中执行的,也会形成死锁。 在一个队列中,就会出现互相等待的现象,恰好同步又很差开启新的线程,这样就会死锁了。
主队列不会开启新的线程,任务按顺序在主线程执行
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//获取主队列
let que = DispatchQueue.main
//添加任务1
que.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务1Thread---", Thread.current) //打印当前线程
}
}
//添加任务2
que.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2) //模拟耗时操做
print("任务2Thread---", Thread.current)
}
}
print("代码块------end")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c4076500>{number = 1, name = main} 代码块------begin 代码块------end 任务1Thread--- <NSThread: 0x1c4076500>{number = 1, name = main} 任务1Thread--- <NSThread: 0x1c4076500>{number = 1, name = main} 任务2Thread--- <NSThread: 0x1c4076500>{number = 1, name = main} 任务2Thread--- <NSThread: 0x1c4076500>{number = 1, name = main}
能够看到onThread
任务执行完了,没有等待任务一、2
的完成(异步当即执行不等待),因此不会死锁。 主队列是串行队列,任务是按顺序一个接一个执行的。
不少时候咱们但愿延迟执行某个任务,这个时候使用DispatchQueue.main.asyncAfter
是很方便的。 这个方法并非立马执行的,延迟执行也不是绝对准确,能够看到,他是在延迟时间事后,把任务追加到主队列,若是主队列有其余耗时任务,这个延迟任务,相对的也要等待任务完成。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//主线程延迟执行
let delay = DispatchTime.now() + .seconds(3)
DispatchQueue.main.asyncAfter(deadline: delay) {
print("asyncAfter---", Thread.current)
}
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c407f900>{number = 1, name = main} 代码块------begin asyncAfter--- <NSThread: 0x1c407f900>{number = 1, name = main}
DispatchWorkItem
是一个代码块,它能够在任意一个队列上被调用,所以它里面的代码能够在后台运行,也能够在主线程运行。 它的使用真的很简单,就是一堆能够直接调用的代码,而不用像以前同样每次都写一个代码块。咱们也可使用它的通知完成回调任务。
作多线程的业务的时候,常常会有需求,当咱们在作耗时操做的时候完成的时候发个通知告诉我这个任务作完了。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立workItem
let workItem = DispatchWorkItem.init {
for _ in 0..<2 {
print("任务workItem---", Thread.current)
}
}
//全局队列(并发队列)执行workItem
DispatchQueue.global().async {
workItem.perform()
}
//执行完以后通知
workItem.notify(queue: DispatchQueue.main) {
print("任务workItem完成---", Thread.current)
}
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c4079300>{number = 1, name = main} 代码块------begin 代码块------结束 任务workItem--- <NSThread: 0x1c42705c0>{number = 5, name = (null)} 任务workItem--- <NSThread: 0x1c42705c0>{number = 5, name = (null)} 任务workItem完成--- <NSThread: 0x1c4079300>{number = 1, name = main}
能够看到咱们使用全局队列异步执行了workItem
,任务执行完,收到了通知。
有些复杂的业务可能会有这个需求,几个队列执行任务,而后把这些队列都放到一个组Group
里,当组里全部队列的任务都完成了以后,Group
发出通知,回到主队列完成其余任务。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立 DispatchGroup
let group = DispatchGroup()
group.enter()
//全局队列(并发队列)执行任务
DispatchQueue.global().async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务1------", Thread.current)//打印线程
}
group.leave()
}
//若是须要上个队列完成后再执行能够用wait
group.wait()
group.enter()
//自定义并发队列执行任务
DispatchQueue.init(label: "com.jackyshan.thread").async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务2------", Thread.current)//打印线程
}
group.leave()
}
//所有执行完后回到主线程刷新UI
group.notify(queue: DispatchQueue.main) {
print("任务执行完毕------", Thread.current)//打印线程
}
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c0261bc0>{number = 1, name = main} 代码块------begin 任务1------ <NSThread: 0x1c046b900>{number = 5, name = (null)} 任务1------ <NSThread: 0x1c046b900>{number = 5, name = (null)} 代码块------结束 任务2------ <NSThread: 0x1c0476b00>{number = 6, name = (null)} 任务2------ <NSThread: 0x1c0476b00>{number = 6, name = (null)} 任务执行完毕------ <NSThread: 0x1c0261bc0>{number = 1, name = main}
两个队列,一个执行默认的全局队列,一个是本身自定义的并发队列,两个队列都完成以后,group获得了通知。 若是把group.wait()
注释掉,咱们会看到两个队列的任务会交替执行。
dispatch_barrier_async
是oc
的实现,Swift
的实现que.async(flags: .barrier)
这样。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列
let que = DispatchQueue.init(label: "com.jackyshan.thread", attributes: .concurrent)
//并发异步执行任务
que.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务0------", Thread.current)//打印线程
}
}
//并发异步执行任务
que.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务1------", Thread.current)//打印线程
}
}
//栅栏方法:等待队列里前面的任务执行完以后执行
que.async(flags: .barrier) {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务2------", Thread.current)//打印线程
}
//执行完以后执行队列后面的任务
}
//并发异步执行任务
que.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务3------", Thread.current)//打印线程
}
}
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c4078a00>{number = 1, name = main} 代码块------begin 代码块------结束 任务0------ <NSThread: 0x1c427d5c0>{number = 5, name = (null)} 任务1------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任务0------ <NSThread: 0x1c427d5c0>{number = 5, name = (null)} 任务1------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任务2------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任务2------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任务3------ <NSThread: 0x1c0470f80>{number = 4, name = (null)} 任务3------ <NSThread: 0x1c0470f80>{number = 4, name = (null)}
能够看到因为任务2
执行的barrier
的操做,任务0和1
交替执行,任务2
等待0和1
执行完才执行,任务3
也是等待任务2
执行完毕。 也能够看到因为barrier
的操做,并无开启新的线程去跑任务。
在使用 GCD 与 dispatch queue 时,咱们常常须要告诉系统,应用程序中的哪些任务比较重要,须要更高的优先级去执行。固然,因为主队列老是用来处理 UI 以及界面的响应,因此在主线程执行的任务永远都有最高的优先级。无论在哪一种状况下,只要告诉系统必要的信息,iOS 就会根据你的需求安排好队列的优先级以及它们所须要的资源(好比说所需的 CPU 执行时间)。虽然全部的任务最终都会完成,可是,重要的区别在于哪些任务更快完成,哪些任务完成得更晚。
用于指定任务重要程度以及优先级的信息,在 GCD 中被称为 Quality of Service(QoS)。事实上,QoS 是有几个特定值的枚举类型,咱们能够根据须要的优先级,使用合适的 QoS 值来初始化队列。若是没有指定 QoS,则队列会使用默认优先级进行初始化。要详细了解 QoS 可用的值,能够参考这个文档,请确保你仔细看过这个文档。下面的列表总结了 Qos 可用的值,它们也被称为 QoS classes。第一个 class 代码了最高的优先级,最后一个表明了最低的优先级:
建立两个队列,优先级都是userInteractive,看看效果:
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列1
let que1 = DispatchQueue.init(label: "com.jackyshan.thread1", qos: .userInteractive, attributes: .concurrent)
//建立并发队列2
let que2 = DispatchQueue.init(label: "com.jackyshan.thread2", qos: .userInteractive, attributes: .concurrent)
que1.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务1------", Thread.current)//打印线程
}
}
que2.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务2------", Thread.current)//打印线程
}
}
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c0073680>{number = 1, name = main} 代码块------begin 代码块------结束 任务1------ <NSThread: 0x1c047cd80>{number = 5, name = (null)} 任务2------ <NSThread: 0x1c0476c40>{number = 3, name = (null)} 任务1------ <NSThread: 0x1c047cd80>{number = 5, name = (null)} 任务2------ <NSThread: 0x1c0476c40>{number = 3, name = (null)}
两个队列的优先级同样,任务也是交替执行,这和咱们预测的同样。
下面把queue1
的优先级改成background
,看看效果:
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列1
let que1 = DispatchQueue.init(label: "com.jackyshan.thread1", qos: .background, attributes: .concurrent)
//建立并发队列2
let que2 = DispatchQueue.init(label: "com.jackyshan.thread2", qos: .userInteractive, attributes: .concurrent)
que1.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务1------", Thread.current)//打印线程
}
}
que2.async {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("任务2------", Thread.current)//打印线程
}
}
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c006afc0>{number = 1, name = main} 代码块------begin 代码块------结束 任务2------ <NSThread: 0x1c4070180>{number = 5, name = (null)} 任务1------ <NSThread: 0x1c006d400>{number = 6, name = (null)} 任务2------ <NSThread: 0x1c4070180>{number = 5, name = (null)} 任务1------ <NSThread: 0x1c006d400>{number = 6, name = (null)}
能够看到queue1
的优先级调低为background
,queue2
的任务就优先执行了。
还有其余的优先级,从高到低,就不一一相互比较了。
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。相似于太高速路收费站的栏杆。能够经过时,打开栏杆,不能够经过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数为0时等待,不可经过。计数为1或大于1时,计数减1且不等待,可经过。
DispatchSemaphore(value: )
:用于建立信号量,能够指定初始化信号量计数值,这里咱们默认1.semaphore.wait()
:会判断信号量,若是为1,则往下执行。若是是0,则等待。semaphore.signal()
:表明运行结束,信号量加1,有等待的任务这个时候才会继续执行。可使用DispatchSemaphore
实现线程同步,保证线程安全。
加入有一个票池,同时几个线程去卖票,咱们要保证每一个线程获取的票池是一致的。 使用DispatchSemaphore
和刚才讲的DispatchWorkItem
来实现,咱们看看效果。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立票池
var tickets = [Int]()
for i in 0..<38 {
tickets.append(i)
}
//建立一个初始计数值为1的信号
let semaphore = DispatchSemaphore(value: 1)
let workItem = DispatchWorkItem.init {
semaphore.wait()
if tickets.count > 0 {
Thread.sleep(forTimeInterval: 0.2)//耗时操做
print("剩余票数", tickets.count, Thread.current)
tickets.removeLast()//去票池库存
}
else {
print("票池没票了")
}
semaphore.signal()
}
//建立并发队列1
let que1 = DispatchQueue.init(label: "com.jackyshan.thread1", qos: .background, attributes: .concurrent)
//建立并发队列2
let que2 = DispatchQueue.init(label: "com.jackyshan.thread2", qos: .userInteractive, attributes: .concurrent)
que1.async {
for _ in 0..<20 {
workItem.perform()
}
}
que2.async {
for _ in 0..<20 {
workItem.perform()
}
}
print("代码块------结束")
}
复制代码
currentThread--- <NSThread: 0x1c407a6c0>{number = 1, name = main} 代码块------begin 代码块------结束 剩余票数 38 <NSThread: 0x1c44706c0>{number = 8, name = (null)} 剩余票数 37 <NSThread: 0x1c0264c80>{number = 9, name = (null)} 剩余票数 36 <NSThread: 0x1c44706c0>{number = 8, name = (null)} ................ 剩余票数 19 <NSThread: 0x1c0264c80>{number = 9, name = (null)} 剩余票数 18 <NSThread: 0x1c44706c0>{number = 8, name = (null)} 剩余票数 17 <NSThread: 0x1c0264c80>{number = 9, name = (null)} 剩余票数 16 <NSThread: 0x1c44706c0>{number = 8, name = (null)} ................ 剩余票数 2 <NSThread: 0x1c44706c0>{number = 8, name = (null)} 剩余票数 1 <NSThread: 0x1c0264c80>{number = 9, name = (null)} 票池没票了 票池没票了
能够看到咱们的资源没有由于形成资源争抢而出现数据紊乱。信号量很好的实现了多线程同步的功能。
DispatchSource provides an interface for monitoring low-level system objects such as Mach ports, Unix descriptors, Unix signals, and VFS nodes for activity and submitting event handlers to dispatch queues for asynchronous processing when such activity occurs. DispatchSource提供了一组接口,用来提交hander监测底层的事件,这些事件包括Mach ports,Unix descriptors,Unix signals,VFS nodes。
Tips: DispatchSource这个class很好的体现了Swift是一门面向协议的语言。这个类是一个工厂类,用来实现各类source。好比DispatchSourceTimer(自己是个协议)表示一个定时器。
基础协议,全部的用到的DispatchSource都实现了这个协议。这个协议的提供了公共的方法和属性: 因为不一样的source是用到的属性和方法不同,这里只列出几个公共的方法
在Swift 3中,能够方便的用GCD建立一个Timer(新特性)。DispatchSourceTimer自己是一个协议。 好比,写一个timer,1秒后执行,而后10秒后自动取消,容许10毫秒的偏差
PlaygroundPage.current.needsIndefiniteExecution = true
public let timer = DispatchSource.makeTimerSource()
timer.setEventHandler {
//这里要注意循环引用,[weak self] in
print("Timer fired at \(NSDate())")
}
timer.setCancelHandler {
print("Timer canceled at \(NSDate())" )
}
timer.scheduleRepeating(deadline: .now() + .seconds(1), interval: 2.0, leeway: .microseconds(10))
print("Timer resume at \(NSDate())")
timer.resume()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute:{
timer.cancel()
})
复制代码
deadline
表示开始时间,leeway
表示可以容忍的偏差。
DispatchSourceTimer也支持只调用一次。
func scheduleOneshot(deadline: DispatchTime, leeway: DispatchTimeInterval = default)
复制代码
DispatchSource中UserData部分也是强有力的工具,这部分包括两个协议,两个协议都是用来合并数据的变化,只不过一个是按照+(加)的方式,一个是按照|(位与)的方式。
DispatchSourceUserDataAdd DispatchSourceUserDataOr
在使用这两种Source的时候,GCD会帮助咱们自动的将这些改变合并,而后在适当的时候(target queue空闲)的时候,去回调EventHandler,从而避免了频繁的回调致使CPU占用过多。
let userData = DispatchSource.makeUserDataAddSource()
var globalData:UInt = 0
userData.setEventHandler {
let pendingData = userData.data
globalData = globalData + pendingData
print("Add \(pendingData) to global and current global is \(globalData)")
}
userData.resume()
let serialQueue = DispatchQueue(label: "com")
serialQueue.async {
for var index in 1...1000 {
userData.add(data: 1)
}
for var index in 1...1000 {
userData.add(data: 1)
}
}
复制代码
Add 32 to global and current global is 32 Add 1321 to global and current global is 1353 Add 617 to global and current global is 1970 Add 30 to global and current global is 2000
NSOperation是基于GCD的一个抽象基类,将线程封装成要执行的操做,不须要管理线程的生命周期和同步,但比GCD可控性更强,例如能够加入操做依赖(addDependency)、设置操做队列最大可并发执行的操做个数(setMaxConcurrentOperationCount)、取消操做(cancel)等。NSOperation做为抽象基类不具有封装咱们的操做的功能,须要使用两个它的实体子类:NSBlockOperation和继承NSOperation自定义子类。NSOperation须要配合NSOperationQueue来实现多线程。
继承Operation
建立一个类,并重写main
方法。当调用start
的时候,会在适当的时候执行main
里面的任务。
class ViewController: UIViewController {
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
let op = JKOperation.init()
op.start()
print("代码块------结束")
}
}
class JKOperation: Operation {
override func main() {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务---", Thread.current)
}
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c007a280>{number = 1, name = main} 代码块------begin 任务--- <NSThread: 0x1c007a280>{number = 1, name = main} 代码块------结束
能够看到自定义JKOperation
,初始化以后,调用start
方法,main
方法里面的任务执行了,是在主线程执行的。 由于咱们没有使用OperationQueue
,因此没有建立新的线程。
初始化BlockOperation
以后,调用start
方法。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
let bop = BlockOperation.init {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务---", Thread.current)
}
bop.start()
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c4070640>{number = 1, name = main} 代码块------begin 任务--- <NSThread: 0x1c4070640>{number = 1, name = main} 代码块------结束
初始化OperationQueue
以后,调用addOperation
,代码块就会自定执行,调用机制执行是有OperationQueue
里面自动实现的。 addOperation
的方法里面实际上是生成了一个BlockOperation
对象,而后执行了这个对象的start
方法。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
OperationQueue.init().addOperation {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务---", Thread.current)
}
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c006a700>{number = 1, name = main} 代码块------begin 代码块------结束 任务--- <NSThread: 0x1c0469900>{number = 5, name = (null)}
能够看到OperationQueue
初始化,默认是生成了一个并发队列,并且执行的是一个异步操做,因此打印任务的线程不是在主线程。
OperationQueue
没有实现串行队列的方法,也没有像GCD
那样实现了一个全局队列。 只有并发队列的实现和主队列的获取。
并发队列的任务是并发(几乎同时)执行的,能够最大发挥CPU多核的优点。 看到有的说经过maxConcurrentOperationCount
设置并发数量1就实现了串行。 其实是不对的,经过设置优先级能够控制队列的任务交替执行,在下面讲到maxConcurrentOperationCount
会实现代码。
//建立并发队列
let queue = OperationQueue.init()
复制代码
OperationQueue
初始化,默认实现的是并发队列。
咱们的主队列是串行队列,任务是一个接一个执行的。
//获取主队列
let queue = OperationQueue.main
复制代码
获取主队列的任务是异步执行的。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//获取主队列
let queue = OperationQueue.main
queue.addOperation {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务1---", Thread.current)
}
queue.addOperation {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务2---", Thread.current)
}
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c0064f80>{number = 1, name = main} 代码块------begin 代码块------结束 任务1--- <NSThread: 0x1c0064f80>{number = 1, name = main} 任务2--- <NSThread: 0x1c0064f80>{number = 1, name = main}
BlockOperation.init {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务1---", Thread.current)
}.start()
复制代码
//建立并发队列
let queue = OperationQueue.init()
queue.addOperation {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务---", Thread.current)
}
复制代码
maxConcurrentOperationCount 默认状况下为-1,表示不进行限制,可进行并发执行。 maxConcurrentOperationCount这个值不该超过系统限制(64),即便本身设置一个很大的值,系统也会自动调整为 min{本身设定的值,系统设定的默认最大值}。
maxConcurrentOperationCount
为1
,实现串行操做。@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列
let queue = OperationQueue.init()
//设置最大并发数为1
queue.maxConcurrentOperationCount = 1
let bq1 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务1---", Thread.current)
}
}
let bq2 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务2---", Thread.current)
}
}
queue.addOperations([bq1, bq2], waitUntilFinished: false)
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c0067880>{number = 1, name = main} 代码块------begin 代码块------结束 任务1--- <NSThread: 0x1c046ad40>{number = 4, name = (null)} 任务1--- <NSThread: 0x1c046ad40>{number = 4, name = (null)} 任务2--- <NSThread: 0x1c046ad40>{number = 4, name = (null)} 任务2--- <NSThread: 0x1c046ad40>{number = 4, name = (null)}
从打印结果能够看到队列里的任务是按串行执行的。 这是由于队列里的任务优先级同样,在只有一个并发队列数的时候,任务按顺序执行。
maxConcurrentOperationCount
为1
,实现并发操做。@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列
let queue = OperationQueue.init()
//设置最大并发数为1
queue.maxConcurrentOperationCount = 1
let bq1 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务1---", Thread.current)
}
}
bq1.queuePriority = .low
let bq2 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务2---", Thread.current)
}
}
bq2.queuePriority = .high
let bq3 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务3---", Thread.current)
}
}
bq3.queuePriority = .normal
queue.addOperations([bq1, bq2, bq3], waitUntilFinished: false)
print("代码块------结束")
}
复制代码
currentThread--- <NSThread: 0x1c4261780>{number = 1, name = main} 代码块------begin 代码块------结束 任务2--- <NSThread: 0x1c0279340>{number = 4, name = (null)} 任务2--- <NSThread: 0x1c0279340>{number = 4, name = (null)} 任务3--- <NSThread: 0x1c0279340>{number = 4, name = (null)} 任务3--- <NSThread: 0x1c0279340>{number = 4, name = (null)} 任务1--- <NSThread: 0x1c0279340>{number = 4, name = (null)} 任务1--- <NSThread: 0x1c0279340>{number = 4, name = (null)}
能够咱们经过设置优先级queuePriority
,实现了队列的任务交替执行了。
maxConcurrentOperationCount
为11
,实现并发操做。@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列
let queue = OperationQueue.init()
//设置最大并发数为1
queue.maxConcurrentOperationCount = 11
let bq1 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务1---", Thread.current)
}
}
let bq2 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务2---", Thread.current)
}
}
queue.addOperations([bq1, bq2], waitUntilFinished: false)
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c407a200>{number = 1, name = main} 代码块------begin 代码块------结束 任务2--- <NSThread: 0x1c42647c0>{number = 4, name = (null)} 任务1--- <NSThread: 0x1c04714c0>{number = 3, name = (null)} 任务2--- <NSThread: 0x1c42647c0>{number = 4, name = (null)} 任务1--- <NSThread: 0x1c04714c0>{number = 3, name = (null)}
maxConcurrentOperationCount
大于1
的时候,实现了并发操做。
waitUntilFinished
阻塞当前线程,直到该操做结束。可用于线程执行顺序的同步。
好比实现两个并发队列按顺序执行。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列1
let queue1 = OperationQueue.init()
let bq1 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务1---", Thread.current)
}
}
let bq2 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务2---", Thread.current)
}
}
queue1.addOperations([bq1, bq2], waitUntilFinished: true)
//建立并发队列2
let queue2 = OperationQueue.init()
let bq3 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务3---", Thread.current)
}
}
let bq4 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务4---", Thread.current)
}
}
queue2.addOperations([bq3, bq4], waitUntilFinished: true)
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c407d1c0>{number = 1, name = main} 代码块------begin 任务1--- <NSThread: 0x1c0467d40>{number = 4, name = (null)} 任务2--- <NSThread: 0x1c0460a00>{number = 3, name = (null)} 任务1--- <NSThread: 0x1c0467d40>{number = 4, name = (null)} 任务2--- <NSThread: 0x1c0460a00>{number = 3, name = (null)} 任务3--- <NSThread: 0x1c0467d40>{number = 4, name = (null)} 任务4--- <NSThread: 0x1c0460a00>{number = 3, name = (null)} 任务3--- <NSThread: 0x1c0467d40>{number = 4, name = (null)} 任务4--- <NSThread: 0x1c0460a00>{number = 3, name = (null)} 代码块------结束
经过设置队列的waitUntilFinished
为true
,能够看到queu1
的任务并发执行完了以后,queue2
的任务才开始并发执行。 并且全部的执行是在代码块------begin
和代码块------结束
之间的。queue1
和queue2
阻塞了主线程。
@IBAction func onThread() {
//打印当前线程
print("currentThread---", Thread.current)
print("代码块------begin")
//建立并发队列
let queue = OperationQueue.init()
let bq1 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务1---", Thread.current)
}
}
let bq2 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务2---", Thread.current)
}
}
let bq3 = BlockOperation.init {
for _ in 0..<2 {
Thread.sleep(forTimeInterval: 0.2)//执行耗时操做
print("任务3---", Thread.current)
}
}
bq3.addDependency(bq1)
queue.addOperations([bq1, bq2, bq3], waitUntilFinished: false)
print("代码块------结束")
}
复制代码
打印结果: currentThread--- <NSThread: 0x1c0065740>{number = 1, name = main} 代码块------begin 代码块------结束 任务1--- <NSThread: 0x1c0660300>{number = 5, name = (null)} 任务2--- <NSThread: 0x1c4071340>{number = 6, name = (null)} 任务1--- <NSThread: 0x1c0660300>{number = 5, name = (null)} 任务2--- <NSThread: 0x1c4071340>{number = 6, name = (null)} 任务3--- <NSThread: 0x1c0660300>{number = 5, name = (null)} 任务3--- <NSThread: 0x1c0660300>{number = 5, name = (null)}
不添加操做依赖
打印结果: currentThread--- <NSThread: 0x1c4072c40>{number = 1, name = main} 代码块------begin 代码块------结束 任务1--- <NSThread: 0x1c0270c80>{number = 7, name = (null)} 任务2--- <NSThread: 0x1c447df00>{number = 4, name = (null)} 任务3--- <NSThread: 0x1c0270cc0>{number = 8, name = (null)} 任务1--- <NSThread: 0x1c0270c80>{number = 7, name = (null)} 任务2--- <NSThread: 0x1c447df00>{number = 4, name = (null)} 任务3--- <NSThread: 0x1c0270cc0>{number = 8, name = (null)}
能够看到任务3
在添加了操做依赖任务1
,执行就一直等待任务1
完成。
NSOperation 提供了queuePriority(优先级)属性,queuePriority属性适用于同一操做队列中的操做,不适用于不一样操做队列中的操做。默认状况下,全部新建立的操做对象优先级都是NSOperationQueuePriorityNormal。可是咱们能够经过setQueuePriority:方法来改变当前操做在同一队列中的执行优先级。
public enum QueuePriority : Int {
case veryLow
case low
case normal
case high
case veryHigh
}
复制代码
当一个操做的全部依赖都已经完成时,操做对象一般会进入准备就绪状态,等待执行。
queuePriority
属性决定了进入准备就绪状态下的操做之间的开始执行顺序。而且,优先级不能取代依赖关系。
若是一个队列中既包含高优先级操做,又包含低优先级操做,而且两个操做都已经准备就绪,那么队列先执行高优先级操做。好比上例中,若是 op1 和 op4 是不一样优先级的操做,那么就会先执行优先级高的操做。
若是,一个队列中既包含了准备就绪状态的操做,又包含了未准备就绪的操做,未准备就绪的操做优先级比准备就绪的操做优先级高。那么,虽然准备就绪的操做优先级低,也会优先执行。优先级不能取代依赖关系。若是要控制操做间的启动顺序,则必须使用依赖关系。
open func cancel()
可取消操做,实质是标记isCancelled
状态。
open var isExecuting: Bool { get }
判断操做是否正在在运行。
open var isFinished: Bool { get }
判断操做是否已经结束。
open var isConcurrent: Bool { get }
判断操做是否处于串行。
open var isAsynchronous: Bool { get }
判断操做是否处于并发。
open var isReady: Bool { get }
判断操做是否处于准备就绪状态,这个值和操做的依赖关系相关。
open var isCancelled: Bool { get }
判断操做是否已经标记为取消。
open func waitUntilFinished()
阻塞当前线程,直到该操做结束。可用于线程执行顺序的同步。
open var completionBlock: (() -> Swift.Void)?
会在当前操做执行完毕时执行 completionBlock。
open func addDependency(_ op: Operation)
添加依赖,使当前操做依赖于操做 op 的完成。
open func removeDependency(_ op: Operation)
移除依赖,取消当前操做对操做 op 的依赖。
open var dependencies: [Operation] { get }
在当前操做开始执行以前完成执行的全部操做对象数组。
open var queuePriority: Operation.QueuePriority
设置当前操做在队列中的优先级。
open func cancelAllOperations()
能够取消队列的全部操做。
open var isSuspended: Bool
判断队列是否处于暂停状态。true为暂停状态,false为恢复状态。可设置操做的暂停和恢复,true表明暂停队列,false表明恢复队列。
open func waitUntilAllOperationsAreFinished()
阻塞当前线程,直到队列中的操做所有执行完毕。
open func addOperation(_ block: @escaping () -> Swift.Void)
向队列中添加一个 NSBlockOperation 类型操做对象。
open func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool)
向队列中添加操做数组,wait 标志是否阻塞当前线程直到全部操做结束。
open var operations: [Operation] { get }
当前在队列中的操做数组(某个操做执行结束后会自动从这个数组清除)。
open var operationCount: Int { get }
当前队列中的操做数。
open class var current: OperationQueue? { get }
获取当前队列,若是当前线程不是在 NSOperationQueue 上运行则返回 nil。
open class var main: OperationQueue { get }
获取主队列。
欢迎关注公众号:jackyshan,技术干货首发微信,第一时间推送。