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,技術乾貨首發微信,第一時間推送。