一.進(jìn)程和線程
計算機(jī)的核心是CPU三痰,它承擔(dān)了所有的計算任務(wù)。它就像一座工廠窜管,時刻在運(yùn)行散劫。假定工廠的電力有限,一次只能供給一個車間使用幕帆。也就是說获搏,一個車間開工的時候,其他車間都必須停工蜓肆。背后的含義就是颜凯,單個CPU一次只能運(yùn)行一個任務(wù)谋币。進(jìn)程就好比工廠的車間,它代表CPU所能處理的單個任務(wù)症概。任一時刻蕾额,CPU總是運(yùn)行一個進(jìn)程,其他進(jìn)程處于非運(yùn)行狀態(tài)彼城。一個車間里诅蝶,可以有很多工人。他們協(xié)同完成一個任務(wù)募壕。線程就好比車間里的工人调炬。一個進(jìn)程可以包括多個線程。車間的空間是工人們共享的舱馅,比如許多房間是每個工人都可以進(jìn)出的缰泡。這象征一個進(jìn)程的內(nèi)存空間是共享的,每個線程都可以使用這些共享內(nèi)存代嗤〖可是,每間房間的大小不同干毅,有些房間最多只能容納一個人宜猜,比如廁所。里面有人的時候硝逢,其他人就不能進(jìn)去了姨拥。這代表一個線程使用某些共享內(nèi)存時,其他線程必須等它結(jié)束渠鸽,才能使用這一塊內(nèi)存叫乌。一個防止他人進(jìn)入的簡單方法,就是門口加一把鎖拱绑。先到的人鎖上門综芥,后到的人看到上鎖,就在門口排隊猎拨,等鎖打開再進(jìn)去膀藐。這就叫"互斥鎖"(Mutual exclusion,縮寫 Mutex)红省,防止多個線程同時讀寫某一塊內(nèi)存區(qū)域额各。還有些房間,可以同時容納n個人吧恃,比如廚房虾啦。也就是說,如果人數(shù)大于n,多出來的人只能在外面等著傲醉。這好比某些內(nèi)存區(qū)域蝇闭,只能供給固定數(shù)目的線程使用。這時的解決方法硬毕,就是在門口掛n把鑰匙呻引。進(jìn)去的人就取一把鑰匙,出來時再把鑰匙掛回原處吐咳。后到的人發(fā)現(xiàn)鑰匙架空了逻悠,就知道必須在門口排隊等著了。這種做法叫做"信號量"(Semaphore)韭脊,用來保證多個線程不會互相沖突童谒。
不難看出,mutex是semaphore的一種特殊情況(n=1時)沪羔。也就是說饥伊,完全可以用后者替代前者。但是蔫饰,因為mutex較為簡單撵渡,且效率高,所以在必須保證資源獨(dú)占的情況下死嗦,還是采用這種設(shè)計。
操作系統(tǒng)的設(shè)計粒氧,因此可以歸結(jié)為三點(diǎn):
(1)以多進(jìn)程形式越除,允許多個任務(wù)同時運(yùn)行;
(2)以多線程形式外盯,允許單個任務(wù)分成不同的部分運(yùn)行摘盆;
(3)提供協(xié)調(diào)機(jī)制,一方面防止進(jìn)程之間和線程之間產(chǎn)生沖突饱苟,另一方面允許進(jìn)程之間和線程之間共享資源孩擂。
摘自<<進(jìn)程與線程的一個簡單解釋-阮一峰>>
二.多線程
1.優(yōu)點(diǎn)
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
2.缺點(diǎn)
創(chuàng)建線程是有開銷的,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、椫吭空間(子線程512KB、主線程1MB蟆沫,也可以使用-setStackSize:設(shè)置寒矿,但必須是4K的倍數(shù),而且最小是16K)肄渗,創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間镇眷,如果開啟大量的線程,會降低程序的性能翎嫡,線程越多欠动,CPU在調(diào)度線程上的開銷就越大。
程序設(shè)計更加復(fù)雜:比如線程之間的通信惑申、多線程的數(shù)據(jù)共享等問題具伍。
三.四種方案對比
1.pthread
- (IBAction)create_pthread:(id)sender {
//聲名線程變量
pthread_t thread;
/*
pthread_create方法介紹:
參數(shù)一:線程對象的地址
參數(shù)二:設(shè)定線程的屬性(例如:子線程占用多少棧空間:512KB圈驼。人芽。。一般設(shè)置為空NULL)
參數(shù)三:子線程執(zhí)行任務(wù)的函數(shù)聲明
參數(shù)四:傳遞給函數(shù)的參數(shù)
返回值:0代表線程創(chuàng)建成功
*/
int result = pthread_create(&thread, NULL, run,NULL);//創(chuàng)建線程
if (!result) {
NSLog(@"線程創(chuàng)建成功");
}
}
//調(diào)用函數(shù)
void *run(void *param) {
for (NSInteger i = 0; i < 10000; i ++) {
NSLog(@"線程:%@正在打印++++++",[NSThread currentThread]);
}
return NULL;
}
2.NSThread
- 方法一
- (IBAction)createThreadMethod1:(id)sender {
//創(chuàng)建線程對象
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"執(zhí)行任務(wù)1"];
//設(shè)置名稱
thread1.name = @"子線程1";
//設(shè)置線程優(yōu)先級
[thread1 setThreadPriority:0.8];
//開啟任務(wù)
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"執(zhí)行任務(wù)2"];
thread2.name = @"子線程2";
[thread2 setThreadPriority:1.0];
[thread2 start];
}
- (void)run:(NSString *)param {
for (NSInteger i = 0; i < 100; i++) {
NSLog(@"%@-%@",[NSThread currentThread],param);
}
}
- 方法二
- (IBAction)createThreadMethod2:(id)sender {
//默認(rèn)開始子線程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"子線程3"];
}
- 方法三
- (IBAction)createThreadMethod3:(id)sender {
//默認(rèn)開啟子線程
[self performSelectorInBackground:@selector(run:) withObject:@"子線程4"];
}
3.GCD
(1)簡介
全稱Grand Central Dispatch绩脆,它是蘋果為多核的并行運(yùn)算提出的解決方案萤厅,會自動合理地利用更多的CPU內(nèi)核(比如雙核、四核)靴迫,最重要的是會自動管理線程的生命周期(創(chuàng)建線程惕味、調(diào)度任務(wù)、銷毀線程)玉锌,只需要告訴GCD要執(zhí)行什么任務(wù),不需要編寫任何管理代碼名挥。同時它使用的也是 c語言,不過由于使用了 Block主守,使得使用起來更加方便.
(2)基本概念
任務(wù)(block):任務(wù)就是將要在線程中執(zhí)行的代碼躺同,將這段代碼用block封裝好,然后將這個任務(wù)添加到指定的執(zhí)行方式(同步執(zhí)行和異步執(zhí)行)丸逸,等待CPU從隊列中取出任務(wù)放到對應(yīng)的線程中執(zhí)行。
同步(sync):前一個任務(wù)沒有執(zhí)行完畢剃袍,后面的任務(wù)不能執(zhí)行黄刚,不開子線程。
異步(async):前一個任務(wù)沒有執(zhí)行完畢民效,后面的任務(wù)可以執(zhí)行憔维。
dispatch_sync: dispatch_sync則是同步扔一個block到queue中,即扔了我就等著畏邢,等到queue排隊把這個block執(zhí)行完了之后业扒,才繼續(xù)執(zhí)行下一行代碼。
dispatch_async:異步扔一個block到queue中舒萎,即扔完我就不管了程储,繼續(xù)執(zhí)行我的下一行代碼。實際上當(dāng)下一行代碼執(zhí)行時,這個block還未執(zhí)行章鲤,只是入了隊列queue摊灭,queue會排隊來執(zhí)行這個block。
隊列:裝載線程任務(wù)的隊形結(jié)構(gòu)败徊。(系統(tǒng)以先進(jìn)先出的方式調(diào)度隊列中的任務(wù)執(zhí)行)帚呼。在GCD中有兩種隊列:串行隊列和并發(fā)隊列。
并發(fā)隊列:線程可以同時一起進(jìn)行執(zhí)行皱蹦。單核CPU的情況下煤杀,是系統(tǒng)在多條線程之間快速的切換。(并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效)
串行隊列:線程只能依次有序的執(zhí)行沪哺。
主隊列:GCD自帶的一種特殊的串行隊列沈自,放在主隊列中的任務(wù),都會放到主線程中執(zhí)行凤粗。
全局隊列:GCD自帶的一種特殊的并行隊列.
GCD總結(jié):將任務(wù)(要在線程中執(zhí)行的操作block)添加到隊列(自己創(chuàng)建或使用全局并發(fā)隊列)酥泛,并且指定執(zhí)行任務(wù)的方式(異步dispatch_async,同步dispatch_sync)
(3)創(chuàng)建
//創(chuàng)建一個串行隊列【參數(shù)一:隊列的唯一標(biāo)識符嫌拣,用于DEBUG柔袁,可為空。參數(shù)二:串行還是并行標(biāo)識】
dispatch_queue_t serialQueue = dispatch_queue_create("A Serial Queue", DISPATCH_QUEUE_SERIAL);
//創(chuàng)建一個并行隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("A Concurrent Queue", DISPATCH_QUEUE_CONCURRENT);
//獲取主隊列
dispatch_queue_t systemSerialQueue = dispatch_get_main_queue();
//獲取全局隊列【參數(shù)一:隊列優(yōu)先級 參數(shù)二:默認(rèn)傳0即可】
dispatch_queue_t systemConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
(4)八種情況
- 異步并行
隊列中的任務(wù)并發(fā)執(zhí)行,會創(chuàng)建一個或多個子線程异逐。如果隊列中的任務(wù)沒有執(zhí)行完畢捶索,系統(tǒng)則會執(zhí)行其他任務(wù)。
- (IBAction)concurrentQueueAsync:(id)sender {
dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:++++++++++",[NSThread currentThread]);
}
});
NSLog(@"%@:打印+完畢",[NSThread currentThread]);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 3; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:----------",[NSThread currentThread]);
}
});
NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x1c4072080>{number = 1, name = main}:打印+完畢
<NSThread: 0x1c4072080>{number = 1, name = main}:打印-完畢
<NSThread: 0x1c026ee00>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c026eac0>{number = 4, name = (null)}:----------
<NSThread: 0x1c026ee00>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c026eac0>{number = 4, name = (null)}:----------
<NSThread: 0x1c026ee00>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c026eac0>{number = 4, name = (null)}:----------
- 異步串行
隊列中的任務(wù)依次執(zhí)行,會創(chuàng)建一個或多個子線程灰瞻。如果隊列中的任務(wù)沒有執(zhí)行完畢腥例,系統(tǒng)則會執(zhí)行其他任務(wù)。
- (IBAction)serialQueueAsync:(id)sender {
dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
for (NSInteger i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:++++++++++",[NSThread currentThread]);
}
});
NSLog(@"%@:打印+完畢",[NSThread currentThread]);
dispatch_async(serialQueue, ^{
for (NSInteger i = 0; i < 3; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:----------",[NSThread currentThread]);
}
});
NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x1c0263300>{number = 1, name = main}:打印+完畢
<NSThread: 0x1c0263300>{number = 1, name = main}:打印-完畢
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:++++++++++
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:----------
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:----------
<NSThread: 0x1c067a3c0>{number = 3, name = (null)}:----------
- 同步并行
隊列中的任務(wù)依次執(zhí)行酝润,不會創(chuàng)建子線程燎竖。只有隊列中的任務(wù)執(zhí)行完畢,系統(tǒng)才會執(zhí)行下一個任務(wù)要销,會阻塞當(dāng)前線程构回。
- (IBAction)concurrentQueueSync:(id)sender {
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(concurrentQueue, ^{
for (NSInteger i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:++++++++++",[NSThread currentThread]);
}
});
NSLog(@"%@:打印+完畢",[NSThread currentThread]);
dispatch_sync(concurrentQueue, ^{
for (NSInteger i = 0; i < 3; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:----------",[NSThread currentThread]);
}
});
NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x1c4069600>{number = 1, name = main}:+++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:+++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:+++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:打印+完畢
<NSThread: 0x1c4069600>{number = 1, name = main}:---------
<NSThread: 0x1c4069600>{number = 1, name = main}:---------
<NSThread: 0x1c4069600>{number = 1, name = main}:---------
<NSThread: 0x1c4069600>{number = 1, name = main}:打印-完畢
- 同步串行
隊列中的任務(wù)依次執(zhí)行,不會創(chuàng)建子線程疏咐。只有隊列中的任務(wù)執(zhí)行完畢纤掸,系統(tǒng)才會執(zhí)行下一個任務(wù),會阻塞當(dāng)前線程。
- (IBAction)serialQueueSync:(id)sender {
dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
for (NSInteger i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:++++++++++",[NSThread currentThread]);
}
});
NSLog(@"%@:打印+完畢",[NSThread currentThread]);
dispatch_sync(serialQueue, ^{
for (NSInteger i = 0; i < 3; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:----------",[NSThread currentThread]);
}
});
NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x1c4069600>{number = 1, name = main}:++++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:++++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:++++++++++
<NSThread: 0x1c4069600>{number = 1, name = main}:打印+完畢
<NSThread: 0x1c4069600>{number = 1, name = main}:----------
<NSThread: 0x1c4069600>{number = 1, name = main}:----------
<NSThread: 0x1c4069600>{number = 1, name = main}:----------
<NSThread: 0x1c4069600>{number = 1, name = main}:打印-完畢
- 主隊列同步執(zhí)行
主隊列同步執(zhí)行會造成死鎖 原因在后面
dispatch_sync(dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"%@:++++++++++",[NSThread currentThread]);
}
});
- 主隊列異步執(zhí)行
隊列中的任務(wù)依次在主線程中執(zhí)行浑塞,不會創(chuàng)建子線程借跪。隊列中的任務(wù)沒有執(zhí)行完畢,系統(tǒng)則會執(zhí)行下一個任務(wù),不會阻塞當(dāng)前線程酌壕。
- (IBAction)mainQueueAsync:(id)sender {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
for (NSInteger i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:----------",[NSThread currentThread]);
}
});
NSLog(@"%@:打印+完畢",[NSThread currentThread]);
dispatch_async(mainQueue, ^{
for (NSInteger i = 0; i < 3; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:++++++++++",[NSThread currentThread]);
}
});
NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x60000007b340>{number = 1, name = main}:打印+完畢
<NSThread: 0x60000007b340>{number = 1, name = main}:打印-完畢
<NSThread: 0x60000007b340>{number = 1, name = main}:----------
<NSThread: 0x60000007b340>{number = 1, name = main}:----------
<NSThread: 0x60000007b340>{number = 1, name = main}:----------
<NSThread: 0x60000007b340>{number = 1, name = main}:++++++++++
<NSThread: 0x60000007b340>{number = 1, name = main}:++++++++++
<NSThread: 0x60000007b340>{number = 1, name = main}:++++++++++
- 全局隊列異步執(zhí)行
隊列中的任務(wù)并發(fā)執(zhí)行掏愁,會創(chuàng)建一個或多個子線程歇由。隊列中的任務(wù)沒有執(zhí)行完畢,系統(tǒng)則會執(zhí)行下一個任務(wù)托猩,不會阻塞當(dāng)前線程印蓖。
- (IBAction)globalQueueAsync:(id)sender {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{
for (NSInteger i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:----------",[NSThread currentThread]);
}
});
NSLog(@"%@:打印+完畢",[NSThread currentThread]);
dispatch_async(globalQueue, ^{
for (NSInteger i = 0; i < 3; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:++++++++++",[NSThread currentThread]);
}
});
NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x60400007b7c0>{number = 1, name = main}:打印+完畢
<NSThread: 0x60400007b7c0>{number = 1, name = main}:打印-完畢
<NSThread: 0x60000027d2c0>{number = 3, name = (null)}:----------
<NSThread: 0x600000461e00>{number = 4, name = (null)}:++++++++++
<NSThread: 0x60000027d2c0>{number = 3, name = (null)}:----------
<NSThread: 0x600000461e00>{number = 4, name = (null)}:++++++++++
<NSThread: 0x600000461e00>{number = 4, name = (null)}:++++++++++
<NSThread: 0x60000027d2c0>{number = 3, name = (null)}:----------
- 全局隊列同步執(zhí)行
隊列中的任務(wù)依次執(zhí)行,不會創(chuàng)建子線程京腥。隊列中的任務(wù)沒有執(zhí)行完畢赦肃,系統(tǒng)不會執(zhí)行下一個任務(wù),會阻塞當(dāng)前線程公浪。
- (IBAction)globalQueueSync:(id)sender {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_sync(globalQueue, ^{
for (NSInteger i = 0; i < 3; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:----------",[NSThread currentThread]);
}
});
NSLog(@"%@:打印+完畢",[NSThread currentThread]);
dispatch_sync(globalQueue, ^{
for (NSInteger i = 0; i < 3; i ++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@:++++++++++",[NSThread currentThread]);
}
});
NSLog(@"%@:打印-完畢",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x60400007b300>{number = 1, name = main}:----------
<NSThread: 0x60400007b300>{number = 1, name = main}:----------
<NSThread: 0x60400007b300>{number = 1, name = main}:----------
<NSThread: 0x60400007b300>{number = 1, name = main}:打印+完畢
<NSThread: 0x60400007b300>{number = 1, name = main}:++++++++++
<NSThread: 0x60400007b300>{number = 1, name = main}:++++++++++
<NSThread: 0x60400007b300>{number = 1, name = main}:++++++++++
<NSThread: 0x60400007b300>{number = 1, name = main}:打印-完畢
(5)主隊列同步執(zhí)行造成死鎖的原因
dispatch_sync(dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"%@:++++++++++",[NSThread currentThread]);
}
});
同步:只有當(dāng)前任務(wù)執(zhí)行完畢他宛,系統(tǒng)才會執(zhí)行下一個任務(wù)。
主隊列:Block中的任務(wù)和dispatch_sync函數(shù)都是需要在主隊列中執(zhí)行的欠气。
Block中的任務(wù)需要主線程執(zhí)行完dispatch_sync函數(shù)才會執(zhí)行厅各,dispatch_sync函數(shù)需要block執(zhí)行完畢才會執(zhí)行其他任務(wù),兩者相互等待预柒,造成死鎖队塘。
- 非主隊列不會造成死鎖的原因
dispatch_sync是在主隊列com.apple.main-thread中執(zhí)行的。
Block中的任務(wù)是在創(chuàng)建的serial.queue隊列中執(zhí)行的宜鸯。
兩者并不是在同一個隊列中執(zhí)行憔古。Block的執(zhí)行不需要等待dispatch_sync函數(shù)執(zhí)行完畢才去執(zhí)行。
- (IBAction)serialQueueSync:(id)sender {
dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"當(dāng)前隊列:%@-%@:++++++++++",dispatch_get_current_queue(),[NSThread currentThread]);
});
NSLog(@"當(dāng)前隊列:%@-%@:打印完畢",dispatch_get_current_queue(),[NSThread currentThread]);
}
控制臺輸出:
當(dāng)前隊列:<OS_dispatch_queue: serial.queue>-<NSThread: 0x6000000790c0>{number = 1, name = main}:++++++++++
當(dāng)前隊列:<OS_dispatch_queue_main: com.apple.main-thread>-<NSThread: 0x6000000790c0>{number = 1, name = main}:打印完畢
- 在同一個隊列同步執(zhí)行兩個任務(wù)是造成死鎖的原因
在程序執(zhí)行到第二個星號位置的dispatch_sync時淋袖,當(dāng)前隊列serial.queue需要執(zhí)行dispatch_sync函數(shù)鸿市,但是dispatch_sync函數(shù)需要等待Block執(zhí)行完畢才會執(zhí)行,因為serial.queue是串行隊列即碗,其中的任務(wù)是一個一個執(zhí)行的焰情。Block任務(wù)排列在第二個星號位置的dispatch_sync函數(shù)的后面,Block中任務(wù)的執(zhí)行需要等待dispatch_sync函數(shù)的執(zhí)行才會執(zhí)行,即造成了兩者互相等待的情況剥懒,所以造成死鎖内舟。
- (IBAction)serialQueueSync:(id)sender {
dispatch_queue_t serialQueue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"打印+++++:%@-%@",dispatch_get_current_queue(),[NSThread currentThread]);
*dispatch_sync(serialQueue, ^{
NSLog(@"打印------:%@-%@",dispatch_get_current_queue(),[NSThread currentThread]);
});
});
}
(5)GCD線程通訊
當(dāng)我們完成了耗時操作,需要回到主線程進(jìn)行UI更新初橘,這時就需要用到線程通訊:
dispatch_async(dispatch_queue_create("A Concurrent Queue", DISPATCH_QUEUE_CONCURRENT), ^{
//耗時操作
dispatch_async(dispatch_get_main_queue(), ^{
//更新UI
});
});
(6)GCD柵欄函數(shù)
-
什么是柵欄函數(shù)
1.GCD柵欄函數(shù)即dispatch_barrier_async函數(shù),它等待所有位于barrier函數(shù)之前的操作執(zhí)行完畢后執(zhí)行,并且在barrier函數(shù)執(zhí)行之后,barrier函數(shù)之后的操作才會得到執(zhí)行验游。
2.當(dāng)任務(wù)需要異步進(jìn)行,但是這些任務(wù)需要分成兩組來執(zhí)行壁却,第一組完成之后才能進(jìn)行第二組的操作。這時候就用了到GCD的柵欄函數(shù)dispatch_barrier_async裸准。 - 柵欄函數(shù)的使用
- (IBAction)barrierMethod:(id)sender {
dispatch_queue_t concurrentQueue = dispatch_queue_create("a concurrent queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)1:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)2:%@",[NSThread currentThread]);
});
dispatch_barrier_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"柵欄任務(wù):%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)3:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"任務(wù)4:%@",[NSThread currentThread]);
});
}
控制臺輸出:
任務(wù)2:<NSThread: 0x604000275bc0>{number = 4, name = (null)}
任務(wù)1:<NSThread: 0x600000274d40>{number = 3, name = (null)}
柵欄任務(wù):<NSThread: 0x604000275bc0>{number = 4, name = (null)}
任務(wù)4:<NSThread: 0x600000274d40>{number = 3, name = (null)}
任務(wù)3:<NSThread: 0x604000275bc0>{number = 4, name = (null)}
上面的柵欄函數(shù)使用
dispatch_barrier_sync(concurrentQueue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"柵欄任務(wù):%@",[NSThread currentThread]);
});
控制臺輸出為:
任務(wù)2:<NSThread: 0x604000277180>{number = 3, name = (null)}
任務(wù)1:<NSThread: 0x604000278a40>{number = 4, name = (null)}
柵欄任務(wù):<NSThread: 0x60400006b600>{number = 1, name = main}
任務(wù)3:<NSThread: 0x604000277180>{number = 3, name = (null)}
任務(wù)4:<NSThread: 0x60000046bb80>{number = 5, name = (null)}
在柵欄函數(shù)的block任務(wù)執(zhí)行完畢以前展东,主線程都是阻塞狀態(tài)。
(7)GCD延遲函數(shù)
- 秒的單位換算
1秒=1000毫秒
1毫秒=1000微秒
1微秒=1000納秒
1納秒=1000皮秒
1皮秒=1000飛秒
- 系統(tǒng)宏定義
#define NSEC_PER_SEC 1000000000ull //10億納秒等于1秒
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
#define DISPATCH_TIME_NOW (0ull)//代表從當(dāng)前立即開始的時間
- 延遲一段代碼3秒以后執(zhí)行
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"在3秒鐘以后輸出這段文字");
});
(8)GCD一次性任務(wù)【可用于設(shè)計單例】
- (IBAction)onceTokenTask:(id)sender {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"在程序運(yùn)行中炒俱,這段代碼只會被執(zhí)行一次");
});
}
(8)dispatch_apply函數(shù)的使用
-
函數(shù)作用
把一項任務(wù)提交到隊列中多次執(zhí)行盐肃,具體是串行執(zhí)行還是并行執(zhí)行由隊列本身決定,dispatch_apply不會立即返回爪膊,在執(zhí)行完畢后才會返回,是同步的調(diào)用砸王。
- (IBAction)gcdApply:(id)sender {
dispatch_queue_t concurrentQueue = dispatch_queue_create("aConcurrentQueue", DISPATCH_QUEUE_SERIAL);
size_t size = 5;
/*
第一個參數(shù):任務(wù)在隊列中執(zhí)行的次數(shù)
第二個參數(shù):任務(wù)執(zhí)行的隊列
Block參數(shù)代表當(dāng)前任務(wù)在隊列中是第幾次執(zhí)行
*/
dispatch_apply(size, concurrentQueue, ^(size_t index) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"當(dāng)前打印:%zu:線程:%@",index,[NSThread currentThread]);
});
NSLog(@"執(zhí)行其他任務(wù):%@",[NSThread currentThread]);
}
控制臺輸出:
當(dāng)前打印:0:線程:<NSThread: 0x604000261940>{number = 1, name = main}
當(dāng)前打印:2:線程:<NSThread: 0x6000004696c0>{number = 3, name = (null)}
當(dāng)前打印:3:線程:<NSThread: 0x60000027b2c0>{number = 4, name = (null)}
當(dāng)前打印:1:線程:<NSThread: 0x6040006623c0>{number = 5, name = (null)}
當(dāng)前打印:4:線程:<NSThread: 0x604000261940>{number = 1, name = main}
執(zhí)行其他任務(wù):<NSThread: 0x604000261940>{number = 1, name = main}
(8)隊列組
-
業(yè)務(wù)場景示例
當(dāng)我們在開發(fā)一個音樂APP時推盛,需要下載很多首音樂,當(dāng)下載完成以后需要通知用戶所有音樂下載完成谦铃,這時我們就可以使用隊列組實現(xiàn)這一場景耘成。 - 方法介紹
//dispatch_group_create()用戶創(chuàng)建一個隊列組
dispatch_group_t group = dispatch_group_create();
//用于添加對應(yīng)隊列組中的未執(zhí)行完畢的任務(wù)數(shù),執(zhí)行一次驹闰,未執(zhí)行完畢的任務(wù)數(shù)加1瘪菌,當(dāng)未執(zhí)行完畢任務(wù)數(shù)為0的時候,才會使dispatch_group_wait解除阻塞和dispatch_group_notify的block執(zhí)行
dispatch_group_enter(group);
//用于減少隊列組中的未執(zhí)行完畢的任務(wù)數(shù)嘹朗,執(zhí)行一次师妙,未執(zhí)行完畢的任務(wù)數(shù)減1,dispatch_group_enter和dispatch_group_leave要匹配屹培,即有加就要有減,不然系統(tǒng)會認(rèn)為group任務(wù)沒有執(zhí)行完畢
dispatch_group_leave(group);
//延時執(zhí)行當(dāng)前任務(wù)默穴,會阻塞當(dāng)前線程
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC));
//在指定的隊列組和隊列中異步執(zhí)行任務(wù)
dispatch_group_async(group, queue, ^{
});
//當(dāng)隊列組中所有任務(wù)執(zhí)行完畢以后執(zhí)行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});
- 使用示例
- (IBAction)queueGroup:(id)sender {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"%@任務(wù)一",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:4.0f];
NSLog(@"%@任務(wù)二",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)全部完成了");
});
NSLog(@"額外任務(wù):%@",[NSThread currentThread]);
}
控制臺輸出:
額外任務(wù):<NSThread: 0x600000065000>{number = 1, name = main}
<NSThread: 0x60000026f4c0>{number = 3, name = (null)}任務(wù)一
<NSThread: 0x600000460440>{number = 4, name = (null)}任務(wù)二
任務(wù)全部完成了
- 不能正確通知隊列組中所有任務(wù)已經(jīng)完成的情況
- (IBAction)queueGroup:(id)sender {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"%@任務(wù)一",[NSThread currentThread]);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:8.0f];
NSLog(@"%@任務(wù)三",[NSThread currentThread]);
});
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:4.0f];
NSLog(@"%@任務(wù)二",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)全部完成了");
});
NSLog(@"額外任務(wù):%@",[NSThread currentThread]);
}
控制臺輸出:
額外任務(wù):<NSThread: 0x600000079440>{number = 1, name = main}
<NSThread: 0x60000046f7c0>{number = 3, name = (null)}任務(wù)一
<NSThread: 0x600000470ac0>{number = 4, name = (null)}任務(wù)二
任務(wù)全部完成了
<NSThread: 0x60000046f7c0>{number = 3, name = (null)}任務(wù)三
我們發(fā)現(xiàn),在dispatch_group_notify函數(shù)調(diào)用以后褪秀,任務(wù)三才執(zhí)行蓄诽。很明顯,這里并沒有實現(xiàn)在所有任務(wù)執(zhí)行完畢以后才去調(diào)用dispatch_group_notify函數(shù)溜歪。解決方案如下:
我們需要使用dispatch_group_enter和dispatch_group_leave函數(shù)
- (IBAction)queueGroup:(id)sender {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
//在每一次開始執(zhí)行任務(wù)以前進(jìn)行計數(shù)加
dispatch_group_enter(group);
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"%@任務(wù)一",[NSThread currentThread]);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:8.0f];
NSLog(@"%@任務(wù)三",[NSThread currentThread]);
//在每一次任務(wù)執(zhí)行完畢以后進(jìn)行計數(shù)減
dispatch_group_leave(group);
});
});
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
[NSThread sleepForTimeInterval:4.0f];
NSLog(@"%@任務(wù)二",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)全部完成了");
});
NSLog(@"額外任務(wù):%@",[NSThread currentThread]);
}
控制臺輸出如下:
額外任務(wù):<NSThread: 0x600000073ec0>{number = 1, name = main}
<NSThread: 0x6000002792c0>{number = 3, name = (null)}任務(wù)一
<NSThread: 0x60000027a000>{number = 4, name = (null)}任務(wù)二
<NSThread: 0x6000002792c0>{number = 3, name = (null)}任務(wù)三
任務(wù)全部完成了
(9)GCD定時器
-
Dispatch Source
Dispatch Source是GCD中的一種基本數(shù)據(jù)類型若专,從字面意思可稱其為調(diào)度源,它用于處理特定的系統(tǒng)底層事件蝴猪,即:當(dāng)一些特定的系統(tǒng)底層事件發(fā)生時调衰,調(diào)度源會捕捉到這些事件,然后可以做相應(yīng)的邏輯處理自阱。 -
Dispatch Source可處理的事件類型
DISPATCH_SOURCE_TYPE_DATA_ADD 自定義的事件嚎莉,變量增加
DISPATCH_SOURCE_TYPE_DATA_OR 自定義的事件,變量OR
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口發(fā)送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_PROC 進(jìn)程監(jiān)聽,如進(jìn)程的退出沛豌、創(chuàng)建一個或更多的子線程趋箩、進(jìn)程收到UNIX信號
DISPATCH_SOURCE_TYPE_READ IO操作,如對文件的操作加派、socket操作的讀響應(yīng)
DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信號時響應(yīng)
DISPATCH_SOURCE_TYPE_TIMER 定時器
DISPATCH_SOURCE_TYPE_VNODE 文件狀態(tài)監(jiān)聽叫确,文件被刪除、移動芍锦、重命名
DISPATCH_SOURCE_TYPE_WRITE IO操作竹勉,如對文件的操作、socket操作的寫響應(yīng)
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//創(chuàng)建 Timer Source
dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 0);
//設(shè)置 Timer Source 的啟動時間娄琉、間隔時間次乓、精度
dispatch_source_set_timer(timerSource, time, 1.0*NSEC_PER_SEC, 0);
//設(shè)置時間回調(diào)
dispatch_source_set_event_handler(timerSource, ^{
NSLog(@"當(dāng)前時間:%@",[NSDate date]);
});
//開始執(zhí)行 Dispatch Source
dispatch_resume(timerSource);
//強(qiáng)引用避免被提前釋放
self.timerSource = timerSource;
4.NSOperation
NSOperation是GCD的封裝吓歇,完全面向?qū)ο蟾躺摺SOperation是抽象類逃呼,需要使用它的子類NSInvocationOperation 和 NSBlockOperation纱昧,或者自定義子類也行搞坝。
(1)NSInvocationOperation的使用
- 同步執(zhí)行任務(wù)【會阻塞當(dāng)前線程】
- (IBAction)syncTask:(id)sender {
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printTask) object:nil];
[invocationOperation start];
NSLog(@"%@:其他任務(wù)",[NSThread currentThread]);
}
- (void)printTask {
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"%@-%@:執(zhí)行打印任務(wù)",dispatch_get_current_queue(),[NSThread currentThread]);
}
控制臺輸出:
<OS_dispatch_queue_main: com.apple.main-thread>-<NSThread: 0x6000002638c0>{number = 1, name = main}:執(zhí)行打印任務(wù)
<NSThread: 0x6000002638c0>{number = 1, name = main}:其他任務(wù)
- 異步執(zhí)行任務(wù)【不會阻塞當(dāng)前線程】
- (IBAction)asyncTask:(id)sender {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(printTask) object:nil];
[queue addOperation:operation];
NSLog(@"%@:其他任務(wù)",[NSThread currentThread]);
}
- (void)printTask {
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"%@-%@:執(zhí)行打印任務(wù)",dispatch_get_current_queue(),[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x60400006e440>{number = 1, name = main}:其他任務(wù)
<OS_dispatch_queue: NSOperationQueue 0x60000022d300 (QOS: UNSPECIFIED)>-<NSThread: 0x60400046d180>{number = 3, name = (null)}:執(zhí)行打印任務(wù)
(2)NSBlockOperation的使用
- 同步執(zhí)行任務(wù) 并發(fā) 會阻塞當(dāng)前線程
- (IBAction)nsblockOperationSync:(id)sender {
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
}];
[operation addExecutionBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
}];
[operation addExecutionBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)三:%@",[NSThread currentThread]);
}];
[operation start];
NSLog(@"%@其他任務(wù)",[NSThread currentThread]);
}
控制臺輸出:
任務(wù)三:<NSThread: 0x60000027d380>{number = 4, name = (null)}
任務(wù)一:<NSThread: 0x60400006fcc0>{number = 1, name = main}
任務(wù)二:<NSThread: 0x6000002778c0>{number = 3, name = (null)}
<NSThread: 0x60400006fcc0>{number = 1, name = main}其他任務(wù)
- 異步執(zhí)行任務(wù) 非主隊列則為并發(fā)隊列 不會阻塞當(dāng)前線程 后添加的operation先執(zhí)行
- (IBAction)nsblockOperationAsync:(id)sender {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
}];
[queue addOperation:operation1];
[queue addOperation:operation2];
NSLog(@"%@其他任務(wù)",[NSThread currentThread]);
}
控制臺輸出:
<NSThread: 0x604000065f40>{number = 1, name = main}其他任務(wù)
任務(wù)二:<NSThread: 0x600000279540>{number = 3, name = (null)}
任務(wù)一:<NSThread: 0x604000263800>{number = 4, name = (null)}
(3)NSOperation操作依賴
- 沒有設(shè)置依賴關(guān)系
- (IBAction)setttingDependency:(id)sender {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:3.0f];
NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0f];
NSLog(@"任務(wù)三:%@",[NSThread currentThread]);
}];
[queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
NSLog(@"%@其他任務(wù)",[NSThread currentThread]);
}
控制臺輸出如下:
<NSThread: 0x600000074d40>{number = 1, name = main}其他任務(wù)
任務(wù)三:<NSThread: 0x604000470340>{number = 3, name = (null)}
任務(wù)二:<NSThread: 0x60000046e980>{number = 4, name = (null)}
任務(wù)一:<NSThread: 0x60000046a2c0>{number = 5, name = (null)}
- 設(shè)置了依賴關(guān)系
- (IBAction)setttingDependency:(id)sender {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:3.0f];
NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0f];
NSLog(@"任務(wù)三:%@",[NSThread currentThread]);
}];
[operation2 addDependency:operation1];
[operation3 addDependency:operation2];
[queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
NSLog(@"%@其他任務(wù)",[NSThread currentThread]);
}
控制臺輸出如下:
<NSThread: 0x600000261d00>{number = 1, name = main}其他任務(wù)
任務(wù)一:<NSThread: 0x60000047a380>{number = 3, name = (null)}
任務(wù)二:<NSThread: 0x60400027e680>{number = 4, name = (null)}
任務(wù)三:<NSThread: 0x60400027e680>{number = 4, name = (null)}
(4)NSOperation設(shè)置最大并發(fā)數(shù)
- (IBAction)settingMaxCount:(id)sender {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 2;
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)一:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)二:%@",[NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"任務(wù)三:%@",[NSThread currentThread]);
}];
[queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
}
控制臺輸出:
12:03:51 任務(wù)二:<NSThread: 0x600000261680>{number = 3, name = (null)}
12:03:51 任務(wù)一:<NSThread: 0x600000261700>{number = 4, name = (null)}
12:03:53 任務(wù)三:<NSThread: 0x604000474040>{number = 5, name = (null)}
(4)線程通信
- (IBAction)threadCommunication:(id)sender {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2.0f];
NSLog(@"耗時任務(wù):%@",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"%@回主線程更新UI",[NSThread currentThread]);
}];
}];
[queue addOperation:operation];
}
控制臺輸出:
耗時任務(wù):<NSThread: 0x60000027e140>{number = 3, name = (null)}
<NSThread: 0x60400006b640>{number = 1, name = main}回主線程更新UI
四.線程安全
-
線程不安全示例 賣票程序
當(dāng)我們運(yùn)行下列程序:
- (IBAction)simultaneousAccess:(id)sender {
//賣出了多少張票 初始為0張
self.soldedTicketsCount = 0;
//剩余多少票 初始為10000張
self.totalTicketsCount = 10000;
NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_threadUnsafety) object:nil];
firstThread.name = @"窗口一";
NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_threadUnsafety) object:nil];
secondThread.name = @"窗口二";
[firstThread start];
[secondThread start];
}
- (void)sellTickets_threadUnsafety {
while (1) {
if (self.totalTicketsCount > 0) {
self.totalTicketsCount --;
self.soldedTicketsCount ++;
NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
} else {
NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
return;
}
}
}
控制臺輸出:
票賣完了:剩余:0賣出了:9999
票賣完了:剩余:0賣出了:9999
可以明顯看到坝咐,最后的輸出結(jié)果是不正確的留储,因為在多個線程同時執(zhí)行一個任務(wù)的時候卜录,就會產(chǎn)生線程安全問題鹅士。ios中解決線程安全問題的辦法有很多逃默,下面介紹四種:@synchronized鹃愤、NSLock、pthread_mutex_t完域、信號量软吐。
(1).@synchronized
- 使用@synchronized加鎖 可以保證同一時刻只有一個線程訪問特定程序
- @synchronized(nil)無效
- @synchronized只對同一個對象有效
- (IBAction)synchronizedMethod:(id)sender {
self.soldedTicketsCount = 0;
self.totalTicketsCount = 10000;
NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_synchronized) object:nil];
firstThread.name = @"窗口一";
NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_synchronized) object:nil];
secondThread.name = @"窗口二";
[firstThread start];
[secondThread start];
}
- (void)sellTickets_synchronized {
@synchronized(self) {
while (1) {
if (self.totalTicketsCount > 0) {
self.totalTicketsCount --;
self.soldedTicketsCount ++;
NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
} else {
NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
return;
}
}
}
}
控制臺最終輸出:
票賣完了:剩余:0賣出了:10000
票賣完了:剩余:0賣出了:10000
(2).NSLock
- NSLock 遵循 NSLocking 協(xié)議,lock 方法是加鎖吟税,unlock 是解鎖凹耙,tryLock 是嘗試加鎖,如果失敗的話返回 NO肠仪,lockBeforeDate: 是在指定Date之前嘗試加鎖肖抱,如果在指定時間之前都不能加鎖,則返回NO异旧。
- (IBAction)nslockMethod:(id)sender {
self.soldedTicketsCount = 0;
self.totalTicketsCount = 10000;
//NSLock對象
self.lock = [[NSLock alloc] init];
NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_nslock) object:nil];
firstThread.name = @"窗口一";
NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_nslock) object:nil];
secondThread.name = @"窗口二";
[firstThread start];
[secondThread start];
}
- (void)sellTickets_nslock {
while (1) {
//加鎖
[self.lock lock];
if (self.totalTicketsCount > 0) {
self.totalTicketsCount --;
self.soldedTicketsCount ++;
NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
//解鎖
[self.lock unlock];
} else {
NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
//解鎖
[self.lock unlock];
return;
}
}
}
控制臺輸出:
票賣完了:剩余:0賣出了:10000
票賣完了:剩余:0賣出了:10000
(3).pthread_mutex_t
- pthread_mutex_t是基于C語言的意述。
- pthread_mutex_init用于創(chuàng)建并初始化pthread_mutex_t。
- pthread_mutex_unlock解鎖
- pthread_mutex_lock加鎖
//聲名一個靜態(tài)變量
static pthread_mutex_t mutex;
- (IBAction)pthread_mutex_tMethod:(id)sender {
self.soldedTicketsCount = 0;
self.totalTicketsCount = 100000;
//創(chuàng)建并初始化
pthread_mutex_init(&mutex, NULL);
NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
firstThread.name = @"窗口一";
NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
secondThread.name = @"窗口二";
NSThread *thirdThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
thirdThread.name = @"窗口三";
NSThread *fourthThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_pthread_mutex_t) object:nil];
fourthThread.name = @"窗口四";
[firstThread start];
[secondThread start];
[thirdThread start];
[fourthThread start];
}
- (void)sellTickets_pthread_mutex_t {
while (1) {
//加鎖
pthread_mutex_lock(&mutex);
if (self.totalTicketsCount > 0) {
self.totalTicketsCount --;
self.soldedTicketsCount ++;
NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
//解鎖
pthread_mutex_unlock(&mutex);
} else {
NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
//解鎖
pthread_mutex_unlock(&mutex);
return;
}
}
}
控制臺輸出:
票賣完了:剩余:0賣出了:100000
票賣完了:剩余:0賣出了:100000
票賣完了:剩余:0賣出了:100000
票賣完了:剩余:0賣出了:100000
(4).semaphore信號量
- 首先要注意吮蛹,信號量和信號是完全兩碼事荤崇。信號量是一個計數(shù)器,常用于處理進(jìn)程或線程的同步問題潮针,特別是對臨界資源訪問的同步术荤。臨界資源可以簡單地理解為在某一時刻只能由一個進(jìn)程或線程進(jìn)行操作的資源。通常每篷,程序?qū)蚕碣Y源的訪問的代碼只是很短的一段瓣戚,但就是這一段代碼引發(fā)了進(jìn)程之間的競態(tài)條件。這段代碼稱為關(guān)鍵代碼段焦读,或者臨界區(qū)子库。對進(jìn)程同步,也就是確保任一時刻只有一個進(jìn)程能進(jìn)入關(guān)鍵代碼段矗晃。
- 信號量是一個資源計數(shù)器仑嗅,對信號量有兩個操作來達(dá)到互斥,分別是P和V操作。 一般情況是這樣進(jìn)行臨界訪問或互斥訪問的: 設(shè)信號量值為1无畔, 當(dāng)一個進(jìn)程A運(yùn)行時,使用資源吠冤,進(jìn)行P操作浑彰,即對信號量值減1,也就是資源數(shù)少了1個拯辙。這時信號量值為0郭变。系統(tǒng)中規(guī)定當(dāng)信號量值為0是,必須等待涯保,直到信號量值不為零才能繼續(xù)操作诉濒。 這時如果進(jìn)程B想要運(yùn)行,那么也必須進(jìn)行P操作夕春,但是此時信號量為0未荒,所以無法減1,即不能P操作及志,也就阻塞片排。這樣就達(dá)到了進(jìn)程A的排他訪問。 當(dāng)進(jìn)程A運(yùn)行結(jié)束后速侈,釋放資源率寡,進(jìn)行V操作。資源數(shù)重新加1倚搬,這時信號量的值變?yōu)?. 這時進(jìn)程B發(fā)現(xiàn)資源數(shù)不為0冶共,信號量能進(jìn)行P操作了,立即執(zhí)行P操作每界。信號量值又變?yōu)?.此時進(jìn)程B占用資源捅僵,系統(tǒng)禁止其他進(jìn)程訪問資源。 這就是信號量來控制互斥的原理
- 方法介紹
//創(chuàng)建一個信號量并制定其信號值
dispatch_semaphore_create(long value);
/*
1.如果信號值大于0 則向下執(zhí)行任務(wù)
2.如果信號值等于0 則在timeout以前 當(dāng)前進(jìn)程一直處于阻塞狀態(tài) 線程不可以訪問
*/
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//信號量加1 喚醒處于wait狀態(tài)的進(jìn)程 線程可以訪問
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
- semaphore使用示例
- (IBAction)semaphoreMethod:(id)sender {
self.soldedTicketsCount = 0;
self.totalTicketsCount = 10000;
//創(chuàng)建一個信號量 信號值設(shè)置為1
self.semaphore = dispatch_semaphore_create(1);
NSThread *firstThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
firstThread.name = @"窗口一";
NSThread *secondThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
secondThread.name = @"窗口二";
NSThread *thirdThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
thirdThread.name = @"窗口三";
NSThread *fourthThread = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets_semaphore) object:nil];
fourthThread.name = @"窗口四";
[firstThread start];
[secondThread start];
[thirdThread start];
[fourthThread start];
}
- (void)sellTickets_semaphore {
while (1) {
//當(dāng)前信號值為1 向下執(zhí)行任務(wù)并且信號值-1
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
if (self.totalTicketsCount > 0) {
self.totalTicketsCount --;
self.soldedTicketsCount ++;
NSLog(@"%@:剩余:%ld賣出了:%ld",[NSThread currentThread],self.totalTicketsCount,self.soldedTicketsCount);
//任務(wù)執(zhí)行完畢 信號值為0需要加1 解除當(dāng)前進(jìn)程的阻塞狀態(tài) 以便其他線程的訪問
dispatch_semaphore_signal(self.semaphore);
} else {
NSLog(@"票賣完了:剩余:%ld賣出了:%ld",self.totalTicketsCount,self.soldedTicketsCount);
//任務(wù)執(zhí)行完畢 信號值為0需要加1 解除當(dāng)前進(jìn)程的阻塞狀態(tài) 以便其他線程的訪問
dispatch_semaphore_signal(self.semaphore);
return;
}
}
}
控制臺輸出:
票賣完了:剩余:0賣出了:10000
票賣完了:剩余:0賣出了:10000
票賣完了:剩余:0賣出了:10000
票賣完了:剩余:0賣出了:10000