術(shù)語
任務(wù):準(zhǔn)備執(zhí)行的操作,通常就是一個(gè)block塊
隊(duì)列:queue,存放任務(wù),管理任務(wù)
進(jìn)程——蘋果電腦里的活動(dòng)監(jiān)視器App里詳細(xì)羅列了操作系統(tǒng)此時(shí)此刻正在運(yùn)行的所有程序,同時(shí)標(biāo)明了每一個(gè)進(jìn)行的程序的所有線程。
線程——將一個(gè)程序都轉(zhuǎn)換成匯編的CPU命令時(shí),由一堆不分叉的Cpu指令組成的路徑(主線程命令和分線程命令)
多線程編程——由多條不分叉的CPU指令所組成的路徑就是多線程編程。優(yōu)點(diǎn):提高資源利用率和程序執(zhí)行效率,用戶體驗(yàn) 缺點(diǎn):1、上下文切換頻率太高會(huì)影響性能 2、資源競爭—>上鎖(類似單例)3、死鎖 4、開辟的線程其實(shí)類似于創(chuàng)建一個(gè)對(duì)象,會(huì)消耗大量內(nèi)存(內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、棧空間(子線程512KB、主線程1MB,也可以使用-setStackSize:設(shè)置,但必須是4K的倍數(shù),而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時(shí)間)5、程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
CPU包括——1、物理CPU(硬件)2、CPU核 (一個(gè)物理CPU可以虛擬出多個(gè)CPU核,一個(gè)CPU核就相當(dāng)于一個(gè)CPU的功能,可以分身,一個(gè)CPU核一個(gè)時(shí)刻只能執(zhí)行一個(gè)CPU命令)
并發(fā)——雖然同一時(shí)間,CPU只能處理1條線程,只有1條線程在執(zhí)行,但是CPU可以快速地在多條線程之間調(diào)度切換,如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象。當(dāng)然如果線程過多,會(huì)消耗大量的CPU資源,同時(shí)每條線程被調(diào)度執(zhí)行的頻次會(huì)降低(線程的執(zhí)行效率降低)
多核就快
首先談一談就是程序執(zhí)行的本質(zhì),我們寫的代碼都首先是轉(zhuǎn)化成一行行的匯編代碼才能被計(jì)算機(jī)所識(shí)別。這一行行的匯編代碼其實(shí)也叫做CPU指令。這里面就涉及到CPU了,CPU就是讀取這些指令的工具。當(dāng)然,有一種匯編代碼也就是CPU指令的結(jié)構(gòu)是從頭到尾不間斷的,也就是這些CPU指令一點(diǎn)也沒有分叉,從頭到尾都是直路,就像高速公路一樣,只有直路,沒有岔路??墒浅绦虻镊攘驮谟冢龅椒植砺窌r(shí)或更復(fù)雜的十字路口時(shí)應(yīng)該怎么辦?肯定不能一根筋呀!于是CPU廠商就想到了一個(gè)方法,就是分身術(shù),當(dāng)遇到分叉路的時(shí)候就像孫悟空一樣,復(fù)制一個(gè)自己來走新出現(xiàn)的分岔路??墒菃栴}來了,如果第二次又遇到分叉路怎么辦呢?兩種思路,第一種再復(fù)制一個(gè)自己。第二種就是用那個(gè)復(fù)制的自己輪流前進(jìn)。第一種方法主要受到CPU本身性能的局限。有的CPU只能復(fù)制一個(gè)自己,算上本身總共才兩個(gè)。也有的CPU可以復(fù)制三個(gè)自己,算上本身總共有4個(gè)。這種具體去跑馬路的車就叫做CPU核,所以你會(huì)發(fā)現(xiàn),CPU越強(qiáng)大,也就是能夠復(fù)制的自己越多,能夠同時(shí)走的分叉路就越多,自然而然,四核就是比雙核快。第二種方法就是讓CPU的寄存器不斷受到考驗(yàn),當(dāng)CPU分配那個(gè)復(fù)制的自己在那一條道路上奔跑時(shí),必須時(shí)時(shí)記錄下來這個(gè)復(fù)制的自己已經(jīng)在每一條分叉路上已經(jīng)跑了多遠(yuǎn),并需要準(zhǔn)確無誤的保存在寄存器中,然后當(dāng)CPU再一次將那個(gè)復(fù)制的自己放在相應(yīng)地分岔路上繼續(xù)跑時(shí),就會(huì)讀取寄存器里面的數(shù)據(jù)來繼續(xù)前進(jìn)。
主線程刷UI
因?yàn)榉志€程不能刷新UI,但是有的時(shí)候明明看見刷新UI的代碼寫在了分線程里,但這并不代表這些代碼會(huì)被分線程(復(fù)制的CPU核)執(zhí)行,分線程主要是負(fù)責(zé)數(shù)據(jù)的下載。(兩種情況:1、下載完成后主動(dòng)回到主線程,告訴主線程自己已經(jīng)完成下載,可以讓主線程執(zhí)行分線程沒有資格執(zhí)行的代碼 2、需要主線程不定期去主動(dòng)檢查也就是說,盡管你把刷新UI的代碼寫在了分線程,但分線程只會(huì)執(zhí)行其中的下載代碼,根本不會(huì)執(zhí)行刷新UI的代碼。刷新UI的代碼會(huì)等到主線程不定期的主動(dòng)檢查分線程時(shí)才會(huì)被執(zhí)行。所以這樣會(huì)出現(xiàn)一些延時(shí),所以應(yīng)該在下載完成后主動(dòng)回到主線程)
時(shí)間片
如果同一個(gè)CPU核去管理多個(gè)線程時(shí),就會(huì)涉及到時(shí)間分配的問題,如果管理的是兩個(gè)線程,線程一有10個(gè)命令,線程二有20個(gè)命令,CPU核會(huì)將時(shí)間片分配給兩個(gè)線程,一一去執(zhí)行完兩個(gè)線程里面的所有命令,當(dāng)一個(gè)線程切換到另一個(gè)線程時(shí),系統(tǒng)會(huì)將當(dāng)前線程的信息保存到寄存器中,等下次切換過來到時(shí)候喚醒寄存器,讀取數(shù)據(jù),從上一次的命令接著處理。這就是上下文切換。上下文切換——時(shí)間片分配(隨機(jī)分配CPU核給那一個(gè)分線程,同時(shí)保存每一分線程上一次執(zhí)行到的位置到寄存器中,告訴CPU核現(xiàn)在該從第幾個(gè)CPU指令開始執(zhí)行)

NSThread
- 創(chuàng)建、啟動(dòng)線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
- 線程相關(guān)用法
// 獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];
// 線程名字
- (void)setName:(NSString *)name;
- (NSString *)name;
// 獲得主線程
+ (NSThread *)mainThread;
// 判斷是否主線程
- (BOOL)isMainThread;
+ (BOOL)isMainThread;
// 創(chuàng)建線程后自動(dòng)啟動(dòng)
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 隱式創(chuàng)建并啟動(dòng)線程
[self performSelectorInBackground:@selector(run) withObject:nil];
// 阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 強(qiáng)制停止線程
+ (void)exit;
GCD全稱Grand Central Dispatch
純C語言函數(shù),為多核并行運(yùn)算而生,自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核),且自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),只需告訴GCD想要執(zhí)行的任務(wù),根本無需寫任何管理線程生命周期的代碼。隊(duì)列管理任務(wù)執(zhí)行順序,同步異步?jīng)Q定任務(wù)執(zhí)行線程。
- GCD創(chuàng)建并發(fā)隊(duì)列
// 創(chuàng)建并發(fā)隊(duì)列(隊(duì)列名稱,隊(duì)列類型)
dispatch_queue_t myQueue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
// 獲得默認(rèn)并發(fā)隊(duì)列(隊(duì)列優(yōu)先級(jí),缺省參數(shù)0)
dispatch_queue_t systemQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 任務(wù)加入并發(fā)隊(duì)列
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
- GCD創(chuàng)建串行隊(duì)列
// 創(chuàng)建串行隊(duì)列(隊(duì)列名稱,隊(duì)列類型)
dispatch_queue_t myQueue = dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL);
// 直接獲取主隊(duì)列 = 特殊串行隊(duì)列
dispatch_queue_t systemQueue = dispatch_get_main_queue();
// 任務(wù)加入串行隊(duì)列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
- GCD同步執(zhí)行任務(wù)
// 繼續(xù)當(dāng)前線程(隊(duì)列,任務(wù))
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- GCD異步執(zhí)行任務(wù)
// 開啟新線程(隊(duì)列,任務(wù))
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- GCD線程通信
// 從子線程回到主線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執(zhí)行耗時(shí)的異步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主線程,執(zhí)行UI刷新操作
});
});
// 對(duì)比performSelector的線程通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- GCD延時(shí)執(zhí)行
// GCD延時(shí)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后異步執(zhí)行這里的代碼...
});
// 對(duì)比performSelector延時(shí)
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 對(duì)比NSTimer延時(shí)
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];
- GCD單例執(zhí)行
// 保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});
- GCD創(chuàng)建定時(shí)器執(zhí)行代碼塊
// 創(chuàng)建Timer
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 設(shè)置定時(shí)器的觸發(fā)時(shí)間(1秒后)和時(shí)間間隔(每隔2秒)
dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC), 2 * NSEC_PER_SEC, 0);
// 設(shè)置Block回調(diào)
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"Timer %@", [NSThread currentThread]);
});
// 開啟定時(shí)器
dispatch_resume(self.timer);
// 取消置空定時(shí)器
dispatch_cancel(self.timer);
self.timer = nil;
- GCD實(shí)現(xiàn)任務(wù)依賴
// 隊(duì)列組管理任務(wù)一和任務(wù)二
dispatch_group_t group = dispatch_group_create();
// 異步執(zhí)行任務(wù)一
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
// 異步執(zhí)行任務(wù)二
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
// 任務(wù)一和任務(wù)二都結(jié)束Block回調(diào)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});
- GCD分線程遍歷
/**
* GCD快速異步遍歷
*/
- (void)apply
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSString *from = @"初始路徑";
NSString *to = @目標(biāo)路徑";
NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *subpaths = [mgr subpathsAtPath:from];
dispatch_apply(subpaths.count, queue, ^(size_t index) {
NSString *subpath = subpaths[index];
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
[mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil]; // 剪切
NSLog(@"%@---%@", [NSThread currentThread], subpath);
});
}
/**
* 對(duì)比傳統(tǒng)異步遍歷
*/
- (void)moveFile {
NSString *from = @"初始路徑";
NSString *to = @目標(biāo)路徑";
NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *subpaths = [mgr subpathsAtPath:from];
for (NSString *subpath in subpaths) {
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil]; // 剪切
});
}
}
NSOperation&&NSOperationQueue
NSOperation是個(gè)并不具備封裝操作能力的抽象類,必須使用它的子類來封裝操作,NSOperation子類包括NSInvocationOperation、NSBlockOperation、自定義子類繼承NSOperation。如果僅僅是實(shí)例化了一個(gè)操作對(duì)象,然后簡簡單單的執(zhí)行這個(gè)操作的start方法,這樣沒有絲毫意義,還不如在.m文件里面寫一個(gè)私有方法來得直接,可是一旦把實(shí)例化的NSOperation對(duì)象放到NSOperationQueue隊(duì)列之中,添加到隊(duì)列里面的操作對(duì)象會(huì)自動(dòng)開始調(diào)用操作對(duì)象的start方法,開始異步在一條新的線程里面執(zhí)行封裝在操作對(duì)象里面的一系列代碼,這就實(shí)現(xiàn)了分線程操作了,好神奇有木有!
- 創(chuàng)建NSInvocationOperation操作
// 創(chuàng)建NSInvocationOperation對(duì)象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
// 默認(rèn)同步,除非添加操作到隊(duì)列
- (void)addOperation:(NSOperation *)op;
- 創(chuàng)建NSBlockOperation操作
// 創(chuàng)建NSBlockOperation
+ (id)blockOperationWithBlock:(void (^)(void))block;
// 添加更多操作(只要任務(wù)數(shù)>1,就會(huì)異步執(zhí)行操作)
- (void)addExecutionBlock:(void (^)(void))block;
- 創(chuàng)建自定義NSOperation操作
重寫父類NSOperation的main方法,在main方法里面寫入我想要封裝的執(zhí)行的任務(wù),但是有一點(diǎn)需要特別注意,就是自己寫的這段代碼很可能會(huì)是在子線程里面異步執(zhí)行,既然是異步操作,自然無法訪問主線程的自動(dòng)釋放池,對(duì)象的內(nèi)存釋放便是需要重點(diǎn)考慮的內(nèi)容。經(jīng)常的做法就是,在封裝想要執(zhí)行的代碼之前,必須先判斷一下這個(gè)操作對(duì)象是否已經(jīng)被取消,因?yàn)橐坏╅_始執(zhí)行進(jìn)入了子線程就無法在判斷isCancelled這個(gè)屬性了,所以必須在開始執(zhí)行封裝的代碼之前,必須先判斷這個(gè)操作對(duì)象是否已經(jīng)被取消。如果判斷已經(jīng)取消,我需要做的就是一些內(nèi)存的管理。
- 創(chuàng)建NSOperation操作依賴
// 一定先讓操作A執(zhí)行完畢,然后才能執(zhí)行操作B
[operationB addDependency:operationA];
?。?!一定注意不能相互依賴
- 監(jiān)聽NSOperation操作執(zhí)行完畢
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
- 添加操作到NSOperationQueue對(duì)列
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
- 設(shè)置操作對(duì)列NSOperationQueue最大并發(fā)數(shù)
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
- 對(duì)列NSOperationQueue的取消、暫停、恢復(fù)
// 取消隊(duì)列操作
- (void)cancelAllOperations;
- (void)cancel;
// 暫停Or恢復(fù)隊(duì)列
- (void)setSuspended:(BOOL)bool;
- (BOOL)isSuspended;
GCD和NSOperation的區(qū)別?
GCD無法控制線程的最大并發(fā)數(shù),而NSOperation可以控制同時(shí)進(jìn)行的線程個(gè)數(shù)!
開辟多線程?
- performSelector
作為NSObject的類別,因此只要是控制器的對(duì)象就可以直接調(diào)用這個(gè)方法。而且不僅僅可以通過performSelectorInBackground將耗時(shí)操作放進(jìn)分線程里,更可以通過performSelectorOnMainThread將分線程里的參數(shù)傳遞到主線程以便進(jìn)一步操作,例如刷新UI。
- NSThread線程類
thread本身就是線的意思。通過NSThread類實(shí)例化一個(gè)對(duì)象。然后直接通過初始化方法就可以像為UIButton增添點(diǎn)擊事件那樣增添一個(gè)在分線程里進(jìn)行的方法。而且可以傳遞給這個(gè)分線程方法一個(gè)無論什么對(duì)象類型的參數(shù)。但是特別注意:通過NSThread類實(shí)例化的對(duì)象所初始化增添的分線程方法必須通過[對(duì)象 start]方法表示開始執(zhí)行分線程方法。而且無論第三方工具如何豐富,本質(zhì)上都是對(duì)NSThread進(jìn)行一系列操作。所以說在任何情況下都可以調(diào)用[NSThread currentThread]這個(gè)類方法獲取到當(dāng)前的線程信息,有點(diǎn)像代碼版的活動(dòng)監(jiān)視器哈!這是蘋果提供的三種方法里面相對(duì)輕量級(jí)的,但需要管理線程的生命周期、同步、加鎖的問題,這會(huì)導(dǎo)致一定得性能開銷。
- GCD隊(duì)列Grand Central Dispatch
偉大的中樞調(diào)度器,充分發(fā)揮CPU內(nèi)核的性能,自動(dòng)管理線程的生命周期,包括創(chuàng)建線程,調(diào)度任務(wù)和銷毀線程。而且使用過程中只需添加任務(wù)即可,無需管理線程。
- NSOperation
一塊資源可能會(huì)被多個(gè)線程共享,通常添加互斥鎖@synchronized(self){}來避免引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題。但是添加互斥鎖會(huì)影響手機(jī)的性能,所以盡量將加鎖和資源搶奪業(yè)務(wù)的邏輯交給服務(wù)器端處理,線程不會(huì)孤立存在,子線程下載圖片,在主線程刷新UI顯示圖片。使用@synchronized(self的鎖對(duì)象){}包起來的代碼同一時(shí)刻只能被一個(gè)線程執(zhí)行,而且加鎖的時(shí)候盡量縮小范圍,范圍越大就越消耗性能。
串行隊(duì)列Vs并行隊(duì)列?
相同點(diǎn):
創(chuàng)建方式都是
dispatch_queue_t queue,只不過創(chuàng)建隊(duì)列時(shí)的標(biāo)志符后面的參數(shù)有了一些變化,主要有(NULL, DISPATCH_QUEUE_SERIAL,DISPATCH_QUEUE_CONCURRENT)三種類型。前兩種等價(jià)都可以表示串行隊(duì)列。而且有一個(gè)細(xì)節(jié)就是標(biāo)識(shí)符的格式是否有要求一時(shí)確定不了!往隊(duì)列里添加任務(wù)的方式一模一樣,都是
dispatch_async(queue, ^{})這種格式。凡是創(chuàng)建的隊(duì)列都是以分線程的形式存在。所以對(duì)于一個(gè)分線程隊(duì)列來說就必須考慮如何返回到主線程。而且這也不是說純粹意義上的將這個(gè)分線程注銷,更多的是想將無法在分線程中執(zhí)行的代碼及時(shí)的傳遞到主線程以便進(jìn)行下一步操作。將分線程中下載完成的數(shù)據(jù)如何讓以參數(shù)的形式傳遞到主線程呢?兩種方法,方法1就是使用控制器的對(duì)象調(diào)用
performSelectorOnMainThread方法。方法2就是通過dispatch_queue_t mainQueue = dispatch_get_main_queue()獲取到主隊(duì)列,這也就代表了主線程。通過在主隊(duì)列中添加任務(wù)也就等于是通過主線程來執(zhí)行分線程無法執(zhí)行的代碼。而且方法2還有一個(gè)優(yōu)點(diǎn)就是,無需像方法一那樣過于糾結(jié)于到底應(yīng)該將分線程的什么參數(shù)傳遞出去,因?yàn)榉椒ǘ侵苯咏⒃诜志€程的基礎(chǔ)上,本質(zhì)上只是起一個(gè)臨時(shí)調(diào)用主線程的功能,因此方法二可以直接調(diào)用分線程里的任何參數(shù)變量。如果不理解什么是串行隊(duì)列,其實(shí)主隊(duì)列
dispatch_queue_t mainQueue = dispatch_get_main_queue()就是一個(gè)典型的串行隊(duì)列,無論你添加多少任務(wù)總是按照任務(wù)的添加順序依次執(zhí)行,沒有例外。而且自始至終都是一個(gè)線程,而且這個(gè)線程就叫做主線程。
不同點(diǎn):
串行隊(duì)列無論添加多少個(gè)任務(wù)都只是創(chuàng)建了一個(gè)分線程,而且執(zhí)行任務(wù)的順序嚴(yán)格按照添加任務(wù)的先后順序進(jìn)行。而對(duì)于并行隊(duì)列而言,往并行對(duì)列中每增添一個(gè)任務(wù)就創(chuàng)建了一個(gè)分線程。也就是說并行隊(duì)列中有多少個(gè)任務(wù)就有多少個(gè)分線程,我們也都知道,線程其實(shí)本質(zhì)上也是一個(gè)對(duì)象,是對(duì)象就必須開辟內(nèi)存,因此雖然說創(chuàng)建很多的分線程有利于獲得良好的用戶體驗(yàn),但這對(duì)于用戶終端的內(nèi)存是一個(gè)極大的考驗(yàn)。
并行隊(duì)列的任務(wù)因?yàn)槭且粋€(gè)任務(wù)對(duì)應(yīng)一個(gè)分線程。所以可以認(rèn)為并行隊(duì)列里的任務(wù)是同時(shí)進(jìn)行的。所以最終并行隊(duì)列里的哪一個(gè)任務(wù)最先執(zhí)行結(jié)束的關(guān)鍵還在于任務(wù)的具體內(nèi)容。
對(duì)于并行隊(duì)列而言有一種很特殊的需求就是:我想準(zhǔn)確地知道并行隊(duì)列里的最后一個(gè)任務(wù)是什么時(shí)候執(zhí)行完畢,然后我再進(jìn)行下一部操作,因?yàn)橥ǔG闆r下是無法準(zhǔn)確地獲取到現(xiàn)在并行隊(duì)列的任務(wù)到底執(zhí)行到了一個(gè)什么階段。根本無法像串行隊(duì)列那樣通過添加任務(wù)的先后順序來推斷出到底哪一個(gè)任務(wù)結(jié)束后標(biāo)志著隊(duì)列任務(wù)全部結(jié)束!解決方案就是給并行隊(duì)列添加一個(gè)
group。同樣需要通過dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT)先創(chuàng)建一個(gè)并行隊(duì)列,然后通過dispatch_group_t group = dispatch_group_create()創(chuàng)建一個(gè)管理并行隊(duì)列里所有任務(wù)的組。接著跟常規(guī)添加任務(wù)到并行隊(duì)列不同的是:dispatch_group_async(group, queue, ^{});當(dāng)然從分線程返回到主線程的方法dispatch_async(dispatch_get_main_queue(), ^{});并沒有發(fā)生變化。把所有的任務(wù)添加到有管理組的并行隊(duì)列中目的就是時(shí)刻監(jiān)聽并行隊(duì)列里的任務(wù)的進(jìn)行階段,監(jiān)聽到并行隊(duì)列里所有的任務(wù)全部結(jié)束時(shí)就會(huì)觸發(fā)方法dispatch_group_notify(group,dispatch_get_main_queue(),^{});需要注意的是,這個(gè)方法里面的代碼全部是由主線程執(zhí)行。因?yàn)椴⑿嘘?duì)列里的所有任務(wù)所對(duì)應(yīng)的分線程也已經(jīng)結(jié)束。監(jiān)聽到任務(wù)結(jié)束自動(dòng)將監(jiān)聽方法里面的代碼交由主線程執(zhí)行。這也就是說,其實(shí)管理并行隊(duì)列里所有任務(wù)的組group的監(jiān)聽方法其實(shí)本質(zhì)上就是就是并行隊(duì)列里所有任務(wù)都結(jié)束后返回到主線程以便進(jìn)行下一步操作。如果我們想要?jiǎng)?chuàng)建多個(gè)并行對(duì)列,那么又如何確定哪一個(gè)并行隊(duì)列率先執(zhí)行呢,答案是,根據(jù)并行隊(duì)列的優(yōu)先級(jí)來進(jìn)行判斷。創(chuàng)建有優(yōu)先級(jí)的并行隊(duì)列的方法為dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)。當(dāng)然在這里將并行隊(duì)列的優(yōu)先級(jí)設(shè)置為了默認(rèn),也就相當(dāng)于又增加了一種創(chuàng)建普通并行隊(duì)列的方法。
如果我想隊(duì)列里的任務(wù)在多少秒之后才開始執(zhí)行,那么最簡單的方法就是在具體的任務(wù)里添加睡眠延時(shí)
sleep(1),但是的但是這也僅僅適用于串行隊(duì)列中,而且就算這樣可以也會(huì)帶來一個(gè)整體任務(wù)進(jìn)行的延時(shí)。對(duì)于并行隊(duì)列來說,只要添加完了任務(wù)就會(huì)全面開始,根本不會(huì)停下來,所以最好的方法就是通過dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 4ull*NSEC_PER_MSEC)來設(shè)置過4秒后再通過dispatch_after(time, queue, ^{})將任務(wù)添加到隊(duì)列queue中。這樣就可以完美的實(shí)現(xiàn)至少在多少秒后再進(jìn)行某一個(gè)任務(wù)。6、無論在串行隊(duì)列還是在并行隊(duì)列中,有時(shí)候我們想要某個(gè)任務(wù)只進(jìn)行一次,就算這是一個(gè)死循環(huán)的隊(duì)列,也要保證某個(gè)任務(wù)只被執(zhí)行一次,方法就是先用static dispatch_once_t onceTaken確定謂詞onceTaken,用來保證dispatch_once(&onceTaken,^{ })方法里的代碼也就是任務(wù)將從始至終只會(huì)被執(zhí)行一次。
互斥鎖
@synchronized(鎖對(duì)象) { // 需要鎖定的代碼 }
static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
@synchronized(self) {
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
+ (instancetype)sharedInstance
{
@synchronized(self) {
if (_instance == nil) {
_instance = [[self alloc] init];
}
}
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點(diǎn):需要消耗大量的CPU資源
前提:多條線程搶奪同一塊資源
線程同步:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))
atomic:原子屬性,為setter方法加鎖(默認(rèn)就是atomic),線程安全,需要消耗大量的資源
nonatomic:非原子屬性,不會(huì)為setter方法加鎖,非線程安全,適合內(nèi)存小的移動(dòng)設(shè)備,盡量避免多線程搶奪同一塊資源,盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動(dòng)客戶端的壓力