一、多線程簡介:
所謂多線程是指一個 進(jìn)程 -- process(可以理解為系統(tǒng)中正在運行的一個應(yīng)用程序)中可以開啟多條 線程 -- thread(線程是進(jìn)程的基本執(zhí)行單元,一個進(jìn)程的所有任務(wù)都在線程中執(zhí)行,每1個進(jìn)程至少要有1條線程),多條線程可以同時執(zhí)行不同的任務(wù) -- task。CPU每一個核同一時間只能執(zhí)行一條線程。多線程對于單核來講就是讓CPU快速的在多個線程之間進(jìn)行調(diào)度(并發(fā))。而多核處理系統(tǒng)可以讓多個線程實現(xiàn)真正的同時進(jìn)行(并行)。
多線程優(yōu)點:
- 可以提高應(yīng)用程序的執(zhí)行效率。
- 可以提高應(yīng)用程序在多核系統(tǒng)上的實時性能,提高資源利用率。
多線程缺點:
- 開啟線程需要一定的內(nèi)存空間,默認(rèn)的,主線程占用1M,一條子線程占用棧區(qū)內(nèi)存512KB,同時,線程過多會導(dǎo)致CPU在線程上調(diào)度上的開銷比較大。
- 在應(yīng)用程序中執(zhí)行多個路徑可能會為代碼增加相當(dāng)多的復(fù)雜性。同時,每個線程必須與其他線程協(xié)調(diào)其動作,以防止它破壞應(yīng)用程序的狀態(tài)信息。因為單個應(yīng)用程序中的線程共享相同的內(nèi)存空間,所以它們可以訪問所有相同的數(shù)據(jù)結(jié)構(gòu)。如果兩個線程同時操作相同的數(shù)據(jù)結(jié)構(gòu),一個線程可能會以破壞生成的數(shù)據(jù)結(jié)構(gòu)的方式覆蓋其他線程的更改。
在iOS中,一個iOS程序運行后,默認(rèn)會開啟1條線程,稱為“主線程”或“UI線程”,主線程的主要作用是顯示\刷新UI界面、處理UI事件,如點擊、滾動、拖拽等事件。需要注意的是:別將比較耗時的操作放到主線程中,會影響程序流暢度。
iOS中常見的多線程技術(shù)一般有三個:NSThread、NSOperation、GCD,其中GCD是目前蘋果官方比較推薦的方式。
二、NSThread:
NSThread可以直接操控線程對象,非常直觀和方便。但是它的生命周期有時需要我們手動管理,一般用來查看當(dāng)前線程是否是主線程。下面來看看它的一些常見用法:
@property (class, readonly, strong) NSThread *currentThread;
: 獲取當(dāng)前線程。一般與isMainThread
結(jié)合判斷當(dāng)前線程是否為主線程。+ (void)detachNewThreadWithBlock:(void (^)(void))block
:采用block形式創(chuàng)建一個新的線程,創(chuàng)建后自動啟動,iOS10.0系統(tǒng)以后才能使用。+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
:采用方法選擇器方法創(chuàng)建線程,創(chuàng)建后自動啟動,可以傳遞參數(shù)。@property (readonly, retain) NSMutableDictionary *threadDictionary;
:每個線程都維護(hù)一個在線程任何地方都能獲取的字典。獲取一個NSMutableDictionary對象后,可以添加我們需要的字段和數(shù)據(jù)。默認(rèn)是空的。注意這里是retain!+ (void)sleepUntilDate:(NSDate *)date;
、+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
:阻塞線程,需要注意的是:線程死了不能復(fù)生!+ (void)exit;
:退出當(dāng)前線程。+ (double)threadPriority;
、+ (BOOL)setThreadPriority:(double)p;
:設(shè)置線程優(yōu)先級,線程優(yōu)先級的取值范圍為0.0~1.0之間,1.0表示線程的優(yōu)先級最高,默認(rèn)為0.5。需要注意的是,不是優(yōu)先級高就會先走當(dāng)前線程,結(jié)束后再走其他線程,依然是來回調(diào)用,但是優(yōu)先級高的線程,調(diào)用的概率、頻率會很大。@property double threadPriority
:優(yōu)先級,將要被廢棄,可以使用下面的屬性替代。@property NSQualityOfService qualityOfService
:該屬性,8.0后才能使用。其中NSQualityOfService是枚舉類型。
// 以下服務(wù)質(zhì)量(QoS)用于向系統(tǒng)表明工作的性質(zhì)和重要性。 它們被系統(tǒng)用于管理各種資源。
// 資源爭奪期間,較高的QoS類別獲得更多的資源(除default外優(yōu)先級依次降低)
typedef NS_ENUM(NSInteger, NSQualityOfService) {
//用于直接涉及提供交互式UI的工作,例如處理事件或繪圖到屏幕
NSQualityOfServiceUserInteractive = 0x21,
//用于執(zhí)行用戶明確請求的工作,并且必須立即呈現(xiàn)哪些結(jié)果,以便進(jìn)一步進(jìn)行用戶交互。
NSQualityOfServiceUserInitiated = 0x19,
//用于執(zhí)行用戶不太可能立即等待結(jié)果的工作。
//這項工作將以節(jié)能的方式運行,在資源受到限制時尊重更高的QoS工作。
NSQualityOfServiceUtility = 0x11,
//用于非用戶啟動或可見的工作。
//它將以最有效的方式運行,同時最大限度地尊重更高的QoS工作。
NSQualityOfServiceBackground = 0x09,
//表示缺少Q(mào)oS信息。每當(dāng)有可能的QoS信息,將從其他來源推斷。
//如果這樣的推斷是不可能的,則使用UserInitiated和Utility之間的QoS。
NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);
@property (nullable, copy) NSString *name
:設(shè)置線程名字。@property NSUInteger stackSize
:設(shè)定新的??臻g大小。??臻g的大小必須在線程的啟動之前設(shè)定,即在調(diào)用NSThread的start方法之前通過setStackSize: 設(shè)置。@property (readonly) BOOL isMainThread
/- (BOOL)isMainThread;
/+ (BOOL)isMainThread;
:判斷當(dāng)前線程是否為主線程。+ (NSThread *)mainThread;
:獲取主線程。- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
:創(chuàng)建線程,需要調(diào)用-start
方法才會開啟線程。- (instancetype)initWithBlock:(void (^)(void))block
:采用block形式創(chuàng)建一個新的線程,同樣需要調(diào)用-start
方法才會開啟線程,10.0以后才能使用。@property (readonly, getter=isExecuting) BOOL executing
:判斷線程是否正在執(zhí)行。@property (readonly, getter=isFinished) BOOL finished
:判斷線程是否已經(jīng)完成。@property (readonly, getter=isCancelled) BOOL cancelled
:判斷線程是否已經(jīng)被取消。- (void)cancel
:取消線程。- (void)start
: 開始線程。- (void)main
:線程入口函數(shù)(包含start功能)。 相當(dāng)于重新走一次線程。
基于NSThread來實現(xiàn)的NSObject的擴(kuò)展方法:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
:在主線程中執(zhí)行,wait表示是否線程任務(wù)完成執(zhí)行,array:指定了線程中 Runloop 的 Modes(有兩種:kCFRunLoopDefaultMode
、kCFRunLoopCommonModes
)。- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
:相當(dāng)于上面的方法指定modes為kCFRunLoopCommonModes
。- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array
/- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
:在指定的線程中運行。
以上四個方法常用于線程間的通訊
-
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg
:所有對象都有能力產(chǎn)生一個新的線程并使用它來執(zhí)行其中的方法。這一方法隱式的創(chuàng)建一個新的線程并執(zhí)行其方法。
二、GCD:
Grand Central Dispatch (GCD)是異步執(zhí)行任務(wù)的技術(shù)之一,它使用了非常簡潔的方法實現(xiàn)了極為復(fù)雜繁瑣的多線編程。
1、隊列的理解:
-
調(diào)度隊列 -- Dispatch Queues :顯示如何在應(yīng)用程序中同時執(zhí)行任務(wù)。一個調(diào)度隊列會順序或同時卻始終執(zhí)行一個先入先出(FIFO)的順序任務(wù)。(換句話說,調(diào)度隊列總是以與隊列中相同的順序進(jìn)行隊列啟動任務(wù)。)調(diào)度隊列用
dispatch_queue_t
類型表示,其創(chuàng)建函數(shù)為dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attire);
,其中第一個參數(shù)用于指定隊列名稱,一般用 逆序全程域名+對象名 的格式來命名,第二個參數(shù)是調(diào)度隊列的屬性可以傳:-
DISPATCH_QUEUE_SERIAL
/NULL
-- 創(chuàng)建活躍的(不需要手動激活的)串行隊列; -
DISPATCH_QUEUE_CONCURRENT
-- 創(chuàng)建活躍的并行隊列; -
DISPATCH_QUEUE_SERIAL_INACTIVE
-- 創(chuàng)建非活躍狀態(tài)的串行隊列,需要調(diào)用dispatch_activate()
函數(shù)激活,未激活隊列無法使用。可以使用dispatch_set_target_queue()
更改處于非活動狀態(tài)的隊列的優(yōu)先級。一旦最初不活動的隊列被激活,優(yōu)先級的更改就不再允許。 -
DISPATCH_QUEUE_CONCURRENT_INACTIVE
-- 創(chuàng)建非活躍狀態(tài)的并行隊列`
-
調(diào)度隊列一般有兩種類型:
串行調(diào)度隊列 -- Serial Dispatch Queues :也稱為 private dispatch queues ,按照任務(wù)被添加到隊列的順序一次執(zhí)行一個任務(wù)。串行隊列通常用于同步對特定資源的訪問。可以根據(jù)需要創(chuàng)建任意數(shù)量的串行隊列,并且每個隊列相對于所有其他隊列并發(fā)運行。也就是說,如果你創(chuàng)建四個串行隊列,則每個隊列一次只會執(zhí)行一個任務(wù),但最多可以同時執(zhí)行四個任務(wù)。
并發(fā)調(diào)度隊列 -- Concurrent Dispatch Queues,這是全局隊列 global dispatch queue 的一種,同時執(zhí)行一個或多個任務(wù),但是任務(wù)仍按照添加到隊列的順序啟動。當(dāng)前執(zhí)行的任務(wù)在由調(diào)度隊列管理的不同線程上運行。
除了自己創(chuàng)建隊列外,還可以獲取系統(tǒng)提供的隊列:
-
主調(diào)度隊列 -- Main Dispatch Queue,主調(diào)度隊列是一個全局可用的 串行隊列,用于執(zhí)行應(yīng)用程序主線程上的任務(wù)。該隊列與應(yīng)用程序的運行循環(huán)(runLoop)一起工作。
- 獲取方法:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
- 獲取方法:
-
全局隊列 -- Global Dispatch Queue,是所有應(yīng)用程序都能夠使用的并發(fā)調(diào)度隊列(Concurrent Dispatch Queues)。
- 獲取方法:
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
- 當(dāng)使用此函數(shù)返回的隊列時調(diào)用
dispatch_suspend()
-- 掛起某個隊列,dispatch_resume()
-- 恢復(fù)某個隊列,dispatch_set_context()
-- 等函數(shù)將不起作用。 - 參數(shù)
identifier
可以使用qos_class_t
中定義的服務(wù)質(zhì)量類或dispatch_queue_priority_t
中定義的優(yōu)先級。下面是其常量定義與對應(yīng)關(guān)系:-
QOS_CLASS_USER_INTERACTIVE
:用戶交互即便額 -
QOS_CLASS_USER_INITIATED
--DISPATCH_QUEUE_PRIORITY_HIGH
: 高優(yōu)先級 -
QOS_CLASS_DEFAULT
--DISPATCH_QUEUE_PRIORITY_DEFAULT
:默認(rèn)優(yōu)先級 -
QOS_CLASS_UTILITY
--DISPATCH_QUEUE_PRIORITY_LOW
:低優(yōu)先級 -
QOS_CLASS_BACKGROUND --
DISPATCH_QUEUE_PRIORITY_BACKGROUND`:后臺執(zhí)行
-
- 參數(shù)
flags
:現(xiàn)階段暫時用不到,一般傳 0。
- 當(dāng)使用此函數(shù)返回的隊列時調(diào)用
- 獲取方法:
2、GCD中有2個用來執(zhí)行任務(wù)的函數(shù):
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
:異步執(zhí)行任務(wù)。void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
:同步執(zhí)行任務(wù)。
關(guān)于同步、異步、并發(fā)、串行的概念,簡單來講同步異步是針對是否開辟新線程來講的,而并發(fā)串行是針對隊列中任務(wù)的執(zhí)行順序來講的,關(guān)于具體概念及關(guān)系:
- 同步 -- sync: 只能在當(dāng)前線程按先后順序依次執(zhí)行,不開啟新線程。
- 異步 -- async: 可以在當(dāng)前線程開啟多個新線程執(zhí)行,可不按順序執(zhí)行。
- 并發(fā) -- concurrency: 線程執(zhí)行可以同時一起進(jìn)行執(zhí)行。
- 串行 -- serial: 線程執(zhí)行只能依次逐一先后有序的執(zhí)行。
?? | 系統(tǒng)全局隊列 | 系統(tǒng)主隊列 | creat串行隊列 | creat并發(fā)隊列 |
---|---|---|---|---|
同步(sync),均不開辟新線程,在本線程上運行 | 不能這樣使用 | 不能這樣使用,會造成死鎖?。?! | 在某一線程上串行 | 在某一線程上串行 |
異步(async) | 每次都會開辟新線程,并發(fā) | 不開辟新線程,在某一線程上串行,獨立直接使用就是主線程上 | 會開辟一條新線程,在新的線程上串行 | 每次都會開辟新線程,并發(fā) |
可以如下示例一樣去驗證以加深理解:
NSLog(@"主線程 == %@", [NSThread currentThread]);
// 調(diào)度隊列類型 dispatch_queue_t
dispatch_queue_t serialQueue, concurrentQueue, mainQueue, globalQueue;
// 調(diào)度隊列創(chuàng)建函數(shù)創(chuàng)建串行隊列
serialQueue = dispatch_queue_create("com.atx610.NewMTX.serialQueue", DISPATCH_QUEUE_SERIAL);
// 調(diào)度隊列創(chuàng)建函數(shù)創(chuàng)建并行隊列
concurrentQueue = dispatch_queue_create("com.atx610.NewMTX.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// 獲取主隊列
mainQueue = dispatch_get_main_queue();
// 獲取全局隊列
globalQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
dispatch_async(serialQueue, ^{
NSLog(@"異步,串行 == %@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"異步,串行 == %@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"異步,串行 == %@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"同步,串行 == %@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"同步,串行 == %@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"同步,串行 == %@", [NSThread currentThread]);
});
3、調(diào)整隊列的優(yōu)先級
對于主隊列和全局隊列的優(yōu)先級是一般是無法更改的,如果更改會發(fā)生不可預(yù)知的狀況,但不一定會崩潰。一般調(diào)整只是針對使用dispatch_queue_creat ()
函數(shù)創(chuàng)建的隊列,且一般更改的是將要進(jìn)行異步的并發(fā)的對列。默認(rèn)生成的隊列優(yōu)先級為默認(rèn)優(yōu)先級,一般操作不立即生效,但是如果隊列處于未激活狀態(tài)則會立即生效。
- 函數(shù):
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t _Nullable queue);
,第一個參數(shù)是目的參數(shù),即想要更改優(yōu)先級的隊列,第二個參數(shù)是目標(biāo)參數(shù)也是隊列,一般使用全局隊列dispatch_get_global_queue ()
,即讓第一個參數(shù)隊列的優(yōu)先級與第二個參數(shù)隊列的優(yōu)先級保持一致。
4、時間函數(shù)的應(yīng)用
在GCD中時間一般使用dispatch_time_t
類型來表示,系統(tǒng)提供了兩個相對時間常量:
-
DISPATCH_TIME_NOW
: 現(xiàn)在 -
DISPATCH_TIME_FOREVER
:永遠(yuǎn)
(1)、創(chuàng)建相對時間:
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
函數(shù)相對于默認(rèn)時鐘創(chuàng)建新的dispatch_time_t
或修改現(xiàn)有的dispatch_time_t
:
- 第一個參數(shù)
when
: 是一個時間參照,可以使用自定義的dispatch_time_t對象,也可以使用通提供的時間常量,一般使用DISPATCH_TIME_NOW
,如果傳入0,那將使用mach_absolute_time()
(是一個CPU依賴函數(shù),返回一個基于系統(tǒng)啟動后的時鐘"嘀嗒"數(shù))的結(jié)果。 - 第二個參數(shù)
delta
:指相對于第一個參數(shù)的時間增量,單位是ull 即納秒數(shù)。1s(秒) = 1000 ms(毫秒)= 1000000 μm(微妙)= 1000000000 ns(納秒),所以這里可以用系統(tǒng)提供的幾個常量:
#define NSEC_PER_SEC 1000000000ull // 1s
#define NSEC_PER_MSEC 1000000ull // 1ms
#define USEC_PER_SEC 1000000ull // 1ms
#define NSEC_PER_USEC 1000ull // 1μm
//舉例,表示距離現(xiàn)在10秒后時間點
dispatch_time_t newTime = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
(2)、創(chuàng)建絕對時間:
dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
這一函數(shù)常用來創(chuàng)建基于一個絕對時間的絕對時間,例如想得到2018年8月8號8時8分8秒向后再添加8秒的時間。
- 第一個參數(shù)
when
: 是一個結(jié)構(gòu)體類型時間參數(shù)(如下:)。其中tv_sec為創(chuàng)建struct timeval時到Epoch的秒數(shù),tv_usec為微秒數(shù),即秒后面的零頭。比如當(dāng)前我敲代碼時的tv_sec為6666666666,tv_usec為666666,即當(dāng)前時間距Epoch時間6666666666秒,666666微秒。
#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC struct timespec
_STRUCT_TIMESPEC
{
__darwin_time_t tv_sec;
long tv_nsec;
};
#endif
- 第二個參數(shù)
delta
:與上面創(chuàng)建相對時間第二個參數(shù)一樣。
使用:
NSString * dateString = @"2018-08-08 08:08:08";
NSDateFormatter * formatDate = [[NSDateFormatter alloc] init];
formatDate.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSDate * yearDate = [formatDate dateFromString:dateString];
NSTimeInterval interval = [yearDate timeIntervalSince1970];
double second, subSecond;
struct timespec time;
// 將浮點數(shù)分解為整數(shù)和小數(shù)
subSecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subSecond * NSEC_PER_SEC;
dispatch_time_t absoluteTime = dispatch_walltime(&time, 8 * NSEC_PER_SEC);
// 這樣就獲得了一個相對于具體時間的絕對時間
(3)、延遲函數(shù):
iOS中做一個延遲一段時間的方法有很多,其中在GDC中可以使用void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
函數(shù)來做延遲操作。
首先來舉個??:
dispatch_time_t newTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_after(newTime, dispatch_get_main_queue(), ^{
NSLog(@"三秒后的我~");
});
在這里需要注意:dispatch_after函數(shù)不是在指定時間后執(zhí)行某種處理,而只是在指定時間將某種處理追加到Dispatch Queue中,但是不一定是立即執(zhí)行該處理!
第一個參數(shù):
when
:指定時間,可以直接使用dispatch_time()或者dispatch_walltime()函數(shù)做參數(shù)。第二個參數(shù):queue
:指定要將處理追加到那個隊列中。第三個參數(shù):block
:要追加的處理。如上面的例子所示,三秒后將追加處理加入主隊列中,因為Main Dispatch Queue在主線程中的RunLoop中執(zhí)行,所以在每隔1/60秒執(zhí)行一次的RunLoop中,Block最快會在三秒后執(zhí)行,最慢會在3 + 1/60秒后執(zhí)行,但是其他線程不一定。
5、調(diào)度組 -- Dispatch groups
Dispatch groups是在一個或多個任務(wù)執(zhí)行完成之前阻止線程的一種方式。類似于將所有任務(wù)加入到串行隊列中,當(dāng)最后一個任務(wù)執(zhí)行完畢后再做下一步操作,但是不同的是Dispatch groups一般針對異步并發(fā)隊列,因為你無法檢測幾個任務(wù)誰是最后執(zhí)行完的,但是這幾個任務(wù)不全部完成就無法進(jìn)行下一步操作,或者下一步操作出現(xiàn)未知狀況,Dispatch groups就是解決這樣的問題的。
使用
dispatch_group_t
表示組類型,其創(chuàng)建函數(shù):dispatch_group_t dispatch_group_create(void);
使用調(diào)度組進(jìn)行異步操作時使用
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
函數(shù)來代替dispatch_async()
函數(shù)。long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
:直到與組關(guān)聯(lián)的所有任務(wù)塊都已完成 或 指定的時間已經(jīng)超過時返回。成功返回0,在即指定時間內(nèi)完成了組相關(guān)的所有任務(wù),錯誤為非0,即超時。void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
:該函數(shù)與上面最大的不同就是會一直等到組關(guān)聯(lián)的所有任務(wù)塊都已完成然后在會將本函數(shù)將執(zhí)行的Block追加到Dispatch Queue中!
以上兩個方法是配合dispatch_group_async ()
一起使用的
void dispatch_group_enter(dispatch_group_t group);
:手動指示Block已進(jìn)入Dispatch Queue。調(diào)用此函數(shù)表示另一個Block通過dispatch_async()的方法加入了組。 調(diào)用此函數(shù)必須與dispatch_group_leave()配合使用。void dispatch_group_leave(dispatch_group_t group);
:手動指示Dispatch Queue中的Block已經(jīng)完成。調(diào)用此函數(shù)表示Block已完成,并通過除dispatch_group_async()之外的方法離開調(diào)度組。
示例:
dispatch_group_t newGroup = dispatch_group_create();
dispatch_queue_t queue_change = dispatch_queue_create("com.atx610.NewMTX.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(newGroup, queue_change, ^{
sleep(1);
NSLog(@"test_1");
});
dispatch_group_async(newGroup, queue_change, ^{
NSLog(@"test_2");
});
dispatch_group_enter(newGroup);
dispatch_async(queue_change, ^{
NSLog(@"test_3");
dispatch_group_leave(newGroup);
});
dispatch_group_enter(newGroup);
dispatch_async(queue_change, ^{
sleep(2);
NSLog(@"test_4");
dispatch_group_leave(newGroup);
});
dispatch_group_notify(newGroup, queue_change, ^{
NSLog(@"done,and start new work");
});
// dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
// long result = dispatch_group_wait(newGroup, time);
// if (result == 0) {
// NSLog(@"在設(shè)置的時間內(nèi),組中的任務(wù)全部完成");
// }else{
// NSLog(@"在設(shè)置的時間內(nèi),組中的任務(wù)未全部完成");
// }
結(jié)果:
test_2
test_3
test_1
test_4
done,and start new work
6、調(diào)度障礙 -- Dispatch Barrier
調(diào)度障礙API是一種向dispatch queue提交障礙Block的機(jī)制,類似于dispatch_async()/ dispatch_sync()API。主要用于在訪問數(shù)據(jù)庫或者文件時,實現(xiàn)高效的讀/寫方案。障礙Block 僅在提交到使用DISPATCH_QUEUE_CONCURRENT屬性 創(chuàng)建的 隊列(并發(fā)隊列)時才有特殊的作用; 在個隊列上,只有在障礙Block前添加到隊列中的所有任務(wù)都已經(jīng)完成,障礙Block才會運行,而在障礙Block完成前,障礙Block之后提交到隊列的任何任務(wù)都將不會運行,直到障礙Block完成。
?需要特別注意的是:當(dāng)提交到全局隊列(global queue)或 未使用 DISPATCH_QUEUE_CONCURRENT屬性創(chuàng)建的隊列時,障礙Block與dispatch_async()/ dispatch_sync()API提交的Block的行為相同!!!
-
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
//異步執(zhí)行障礙Block -
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
//同步執(zhí)行障礙Block
示例:
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.atx610.NewMTX.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
NSLog(@"test_1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"test_2");
sleep(2);
NSLog(@"睡醒了~");
});
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"I Am Barrier!");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"test_3");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"test_4");
});
使用create函數(shù)創(chuàng)建的隊列運行結(jié)果如下:
test_1
test_2
睡醒了~
I Am Barrier!
test_3
test_4
使用全局隊列運行結(jié)果如下:
test_1
test_2
I Am Barrier!
test_3
test_4
睡醒了~
7、與執(zhí)行次數(shù)相關(guān)的函數(shù):
(1)、dispatch_apply 函數(shù):
?該函數(shù)功能:將一個任務(wù)提交給調(diào)度隊列進(jìn)行多次調(diào)用。此函數(shù)在返回之前等待任務(wù)塊完成,即 會將指定次數(shù)的指定Block追加到指定的隊列中并等待全部處理執(zhí)行結(jié)束! 如果目標(biāo)隊列是并發(fā)的,則該塊可能會同時被調(diào)用。
-
void dispatch_apply(size_t iterations, dispatch_queue_t queue, DISPATCH_NOESCAPE void (^block)(size_t));
:第一個參數(shù)iterations
表示重復(fù)次數(shù),第三個參數(shù)block中有一個參數(shù),表示該block按照重復(fù)次數(shù)添加到queue中時是第幾個添加的。
常見使用方式:
dispatch_queue_t globalQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
dispatch_async(globalQueue, ^{
dispatch_apply(5, globalQueue, ^(size_t index) {
// 在次并列處理某些事件
NSLog(@"%zu", index);
});
NSLog(@"apply循環(huán) 結(jié)束");
// 只有apply中的事件全部處理完畢了才會進(jìn)行后面的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 主隊列更新界面等操作
NSLog(@"主隊列更新");
});
});
0
1
2
3
4
apply循環(huán) 結(jié)束
主隊列更新
(2)、dispatch_once 函數(shù):
?dispatch_once_t 類型來定義一個變量,dispatch_once 函數(shù)來保證在應(yīng)用程序中只執(zhí)行一次指定處理的任務(wù)。
-
void dispatch_once(dispatch_once_t *predicate, DISPATCH_NOESCAPE dispatch_block_t block);
:第一個參數(shù)是指向dispatch_once_t的指針,用于測試塊是否已完成。其主要用于實現(xiàn)單例模式:
+ (instancetype)shareManager {
static dispatch_once_t onceToken;
static Manager * manager;
dispatch_once(&onceToken, ^{
manger = [[self alloc] init];
});
return manager;
}
8、調(diào)度信號量 -- Dispatch Semaphore
調(diào)度信號量用dispatch_semaphore_t表示,調(diào)度信號量與傳統(tǒng)信號量相似,但通常效率更高。 調(diào)度信號量只有當(dāng)因為信號量不可用而線程需要被阻塞時才調(diào)用到內(nèi)核。 如果信號量可用,則不進(jìn)行內(nèi)核調(diào)用。
dispatch_semaphore_t dispatch_semaphore_create(long value);
創(chuàng)建具有初始值的新計數(shù)信號量,傳遞小于零的值將導(dǎo)致返回NULL。long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
;作用:減少計數(shù)信號量,將信號量減1.第二個參數(shù)是超時時間。成功返回零,如果發(fā)生超時則返回非零值。如果在時間內(nèi),當(dāng)信號總量少于等于0的時候,這個函數(shù)就阻塞當(dāng)前線程等待timeout,如果等待的期間信號量值被dispatch_semaphore_signal函數(shù)加1了,即該函數(shù)所處線程獲得了信號量,那么就繼續(xù)向下執(zhí)行并將信號量減1。如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執(zhí)行其后語句。long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
:增加計數(shù)信號量, 如果以前的值等于零,此函數(shù)會在返回之前喚醒等待的線程。如果線程被喚醒,此函數(shù)返回非零值。 否則返回零。當(dāng)返回值為0時表示當(dāng)前并沒有線程等待其處理的信號量,其處理的信號量的值加1即可。當(dāng)返回值不為0時,表示其當(dāng)前有一個或多個線程等待其處理的信號量,并且該函數(shù)喚醒了一個等待的線程(當(dāng)線程有優(yōu)先級時,喚醒優(yōu)先級最高的線程;否則隨機(jī)喚醒)。
使用:
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semap = dispatch_semaphore_create(0);
dispatch_async(global, ^{
dispatch_semaphore_wait(semap, DISPATCH_TIME_FOREVER);
NSLog(@"first");
});
NSLog(@"second");
dispatch_semaphore_signal(semap);
結(jié)果:
second
first
9、其他常見使用GCD函數(shù)
void dispatch_suspend(dispatch_object_t object);
:暫停在調(diào)度對象上的剩余任務(wù)。這會增加內(nèi)部暫停計數(shù),調(diào)用dispatch_suspend()必須與調(diào)用dispatch_resume()進(jìn)行平衡。void dispatch_resume(dispatch_object_t object);
: 是dispatch_suspend()的反向操作,并且消耗暫停計數(shù)。當(dāng)最后一個暫停計數(shù)被消耗時,即暫停計數(shù)為零時,與該對象關(guān)聯(lián)的任務(wù)將再次被調(diào)用。如果指定的對象具有零暫停計數(shù),并且不是非活動的源,則此函數(shù)將導(dǎo)致斷言并且進(jìn)程終止,即Crash。
三、NSOperation / NSOperationQueue:
操作隊列是一種面向?qū)ο蟮姆椒▉矸庋b要異步執(zhí)行的工作,顯示如何使用Objective-C對象來封裝和執(zhí)行任務(wù)。 NSOperation本身是抽象類,只能使用其子類,另外Foundation框架提供了兩個具體的子類:NSBlockOperation
、NSInvocationOperation
,除此之外還可以自定義繼承自NSOperation的類并實現(xiàn)內(nèi)部相應(yīng)的方法來使用NSOperation。一般NSOperation和NSOperationQueue結(jié)合使用實現(xiàn)多線程并發(fā)。
1、關(guān)于抽象類NSOperation:
- (void)start;
: 開啟任務(wù)操作。NSOperation對象默認(rèn)按同步方式執(zhí)行,也就是在調(diào)用start方法的那個線程中直接執(zhí)行。- (void)main;
: 類的主函數(shù),一般在自己定義繼承自NSOperation的類中重寫該方法以實現(xiàn)執(zhí)行任務(wù)。@property (readonly, getter=isCancelled) BOOL cancelled;
: 操作是否已經(jīng)取消。- (void)cancel;
:operation開始執(zhí)行之后, 默認(rèn)會一直執(zhí)行操作直到完成,我們也可以調(diào)用cancel方法中途取消操作。@property (readonly, getter=isExecuting) BOOL executing;
:操作是否正在執(zhí)行。@property (readonly, getter=isFinished) BOOL finished;
:操作是否已經(jīng)完成。@property (readonly, getter=isAsynchronous) BOOL asynchronous;
:是否是異步的。@property (readonly, getter=isReady) BOOL ready;
:是否準(zhǔn)備就緒。線程start后并不是立即執(zhí)行,而是進(jìn)入一個就緒的狀態(tài)(isReady),由系統(tǒng)調(diào)度執(zhí)行。- (void)addDependency:(NSOperation *)op;
:添加依賴關(guān)系。- (void)removeDependency:(NSOperation *)op;
:移除依賴關(guān)系。@property (readonly, copy) NSArray<NSOperation *> *dependencies;
依賴關(guān)系數(shù)組。@property NSOperationQueuePriority queuePriority;
:優(yōu)先級,具體如下:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
-@property (nullable, copy) void (^completionBlock)(void);
:任務(wù)操作完成后的Block
- (void)waitUntilFinished;
:阻塞當(dāng)前線程直到此任務(wù)執(zhí)行完畢。@property double threadPriority;
:線程的優(yōu)先級@property NSQualityOfService qualityOfService;
:服務(wù)質(zhì)量,具體如下:
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);
-
@property (nullable, copy) NSString *name ;
:給操作起個名字,做標(biāo)記。
2、創(chuàng)建NSInvocationOperation對象:
?主要是使用- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
這一方法,在你指定的對象上調(diào)用你指定的方法,還可以傳遞一個參數(shù)。如下所示:
NSString * name = @"lucy";
NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logSomethingWithString:) object:name];
[invocationOperation start];
--------------
- (void)logSomethingWithString:(NSString *)name
{
NSLog(@"%@", name);
}
3、創(chuàng)建NSBlockOperation對象:
?相對而言NSBlockOperation要比NSInvocationOperation好用一點,該類有兩個方法和一個屬性:
-
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
:創(chuàng)建并添加任務(wù)。 -
- (void)addExecutionBlock:(void (^)(void))block;
:添加并發(fā)任務(wù)。 -
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;
:獲取并發(fā)的block。
示例:
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"001");
}];
[blockOperation addExecutionBlock:^{
NSLog(@"002");
}];
[blockOperation addExecutionBlock:^{
NSLog(@"003");
}];
[blockOperation addExecutionBlock:^{
NSLog(@"004");
}];
[blockOperation start];
NSLog(@"%@", blockOperation.executionBlocks);
4、NSOperationQueue -- 操作隊列:
?操作隊列提供一個異步執(zhí)行的線程,同NSOperation配合使用可以高效利用管理操作和線程。
-
- (void)addOperation:(NSOperation *)op;
:添加任務(wù)。 -
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
:添加一組任務(wù),并設(shè)置是否阻塞當(dāng)前線程知道操作完成。 -
- (void)addOperationWithBlock:(void (^)(void))block ;
:添加一個block形式的operation。 -
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
:獲取隊列中的操作。 -
@property (readonly) NSUInteger operationCount ;
:獲取隊列中的操作的數(shù)量。 -
@property NSInteger maxConcurrentOperationCount;
:設(shè)置、獲取最大并發(fā)數(shù)量。設(shè)置完后如果任務(wù)數(shù)量超過了最大并發(fā)數(shù)量,會等待某一任務(wù)完成后再執(zhí)行。 -
@property (getter=isSuspended) BOOL suspended;
:隊列是否已經(jīng)暫停。當(dāng)設(shè)置為YES后,它會暫停在此之后添加到隊列中或正在運行任務(wù)之后的任務(wù),在此之前添加的或者正在運行的任務(wù)不會受到影響。繼續(xù)已暫停的隊列只需將該屬性設(shè)為NO即可。 -
@property (nullable, copy) NSString *name;
:隊列標(biāo)識。 -
@property NSQualityOfService qualityOfService;
:服務(wù)質(zhì)量。 -
@property (nullable, assign) dispatch_queue_t underlyingQueue;
:潛在隊列,此屬性的默認(rèn)值為nil??梢詫⒋藢傩缘闹翟O(shè)置為現(xiàn)有的某一隊列,以使本排隊的操作任務(wù)可以散布提交給該調(diào)度隊列中執(zhí)行。需要注意:只有當(dāng)前隊列中沒有任何操作任務(wù)時,才能設(shè)置此屬性的值; 如果operationCount不等于0時設(shè)置此屬性的值會引發(fā)NSInvalidArgumentException。 該屬性的值不能是dispatch_get_main_queue返回的值。 為潛在隊列設(shè)置的服務(wù)質(zhì)量級別將覆蓋當(dāng)前隊列的qualityOfService
屬性值。 -
- (void)cancelAllOperations;
:取消所有操作。 -
- (void)waitUntilAllOperationsAreFinished;
:阻塞當(dāng)前線程,等待所有操作任務(wù)都完成。 -
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue
:獲取執(zhí)行當(dāng)前的NSOperation的NSOperationQueue列隊。 -
@property (class, readonly, strong) NSOperationQueue *mainQueue
:獲取系統(tǒng)主線程的NSOperationQueue列隊。
示例:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
imageViewArray = [NSMutableArray array];
for (int i = 0; i < 3; i++) {
UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100 + 100*i, 100, 100)];
[imageViewArray addObject:imageView];
[self.view addSubview:imageView];
}
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"001 --- 001, thread == %@", [NSThread currentThread]);
[imageViewArray[0] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504690123016&di=00957249b318d00f04c80439063671e0&imgtype=0&src=http%3A%2F%2Finuyasha.manmankan.com%2FUploadFiles_6178%2F201004%2F20100427165757723.jpg"]]]];
}];
NSBlockOperation * blockOperation_copy = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"copy --- 001, thread == %@", [NSThread currentThread]);
[imageViewArray[1] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504690048974&di=ddc4518682a2e884cd9e88bbf7d9aa00&imgtype=0&src=http%3A%2F%2Fimg.qqu.cc%2Fuploads%2Fallimg%2F150601%2F1-150601201258.jpg"]]]];
}];
NSBlockOperation * blockOperation_black = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"black --- 001, thread == %@", [NSThread currentThread]);
[imageViewArray[2] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504689993435&di=c52ca4a5d5f46fc2552d3e97dd4eb241&imgtype=0&src=http%3A%2F%2Fp4.gexing.com%2Fshaitu%2F20120825%2F1336%2F5038647615f85.jpg"]]]];
}];
// 添加依賴關(guān)系
[blockOperation addDependency:blockOperation_copy];
[blockOperation_copy addDependency:blockOperation_black];
queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperations:@[blockOperation, blockOperation_copy, blockOperation_black] waitUntilFinished:NO];
[queue setSuspended:YES];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[queue setSuspended:NO];
}