什么是進程?
-
進程
是指在系統(tǒng)中正在運行的一個應(yīng)用程序。 - 每個進程之間是
獨立的
,每個進程均運行在其專用且受保護
的內(nèi)存空間內(nèi)。
什么是線程?
- 1個進程要想執(zhí)行任務(wù),
必須
得有線程(每1個進程至少要有1條線程
)。 - 線程是進程的基本執(zhí)行單元,一個進程(程序)的所有任務(wù)都在線程中執(zhí)行。
小拓展
:
- 線程的串行(就像烤串一樣)
- 1個線程中任務(wù)的執(zhí)行是串行的。
- 如果要在1個線程中執(zhí)行多個任務(wù),那么只能一個一個地按順序執(zhí)行這些任務(wù)。
- 在`同一時間內(nèi)`,1個線程只能執(zhí)行1個任務(wù)。
什么是多線程?
1個進程中可以開啟多條線程,每條線程可以并行(同時)執(zhí)行不同的任務(wù)。
-
線程的并行(同時執(zhí)行)
- 比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B、文件C。
-
多線程并發(fā)執(zhí)行的原理:
- 在同一時間里,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)。
- 多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換),如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象。(如下圖)
CPU調(diào)用線程
多線程優(yōu)缺點:
- 優(yōu)點
- 能適當(dāng)提高程序的執(zhí)行效率。
- 能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
- 缺點
- 開啟線程需要占用一定的內(nèi)存空間(默認情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能。
- 線程越多,CPU在調(diào)度線程上的開銷就越大。
- 程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
多線程在iOS開發(fā)中的應(yīng)用
- 主線程
- 一個iOS程序運行后,默認會在自己的進程中開啟1條線程,稱為“主線程”也叫“UI線程”。
- 作用:刷新顯示UI,處理UI事件。
- 使用注意
- 不要將耗時操作放到主線程中去處理,因為會卡住主線程,造成UI卡頓(用戶體驗差)。
- 和UI相關(guān)的刷新操作`必須`放到主線程中進行處理。
線程的狀態(tài)
- 線程的各種狀態(tài):新建-就緒-運行-阻塞-死亡
- 常用的控制線程狀態(tài)的方法
[NSThread exit];//退出當(dāng)前線程 [NSThread sleepForTimeInterval:7.0];//阻塞線程 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:7.0]];//阻塞線程
注意:線程死亡后不能復(fù)生
線程安全:
- 前提:多個線程同時訪問同一塊資源會發(fā)生數(shù)據(jù)安全問題
解決方案
:加互斥鎖 - 相關(guān)代碼:@synchronized(self){}
- 專業(yè)術(shù)語-線程同步
- 原子和非原子屬性(是否對setter方法加鎖)
IOS中多線程的實現(xiàn)方案
方案 | 簡介 | 語言 | 線程生命周期 | 使用頻率 |
---|---|---|---|---|
pthread | 一套通用的多線程API (跨平臺\可移植) |
C語言 | 程序員管理 | 幾乎不用 |
NSThread |
使用更加面向?qū)ο? (簡單易用,可直接操作線程對象) |
OC語言 | 程序員管理 | 偶爾使用 |
GCD |
為了替代NSThread為生 ( 充分利用設(shè)備多核 ) |
C語言 | 系統(tǒng)自動管理 | 經(jīng)常使用 |
NSOperation |
基于GCD ( 更加面向?qū)ο?更方便地設(shè)置線程之間的依賴 監(jiān)聽線程狀態(tài)KVO ) |
OC語言 | 系統(tǒng)自動管理 | 經(jīng)常使用 |
pthread簡單使用
1.包含頭文件(必須)
#import <pthread.h>
2.創(chuàng)建線程
// 創(chuàng)建線程
/**
*
* 參數(shù)一:線程對象(傳地址)
* 參數(shù)二:線程的屬性(名稱\優(yōu)先級)
* 參數(shù)三:只想函數(shù)的指針
* 參數(shù)四:函數(shù)需要接受的字符串參數(shù),可以不傳遞(注:由于我們創(chuàng)建的是OC的字符串,所以在傳值的時候需要將其轉(zhuǎn)換成C的字符串)
*/
pthread_t thread;
NSString *num = @"123";
pthread_create(&thread, NULL, task, (__bridge void *)(num));
3.定義參數(shù)所需要的函數(shù)指針
void *task(void *num)
{
NSLog(@"當(dāng)前線程 -- %@,傳入的參數(shù):-- %@", [NSThread currentThread], num);
return NULL;
}
如果需要退出線程的話只需調(diào)用下面代碼
pthread_exit(NULL);
運行結(jié)果:
NSThread簡單使用
這邊介紹NSThread創(chuàng)建線程的4種方式:
- 第一種 (alloc nitWithTarget:selector:object:)
- 特點:需要手動開啟線程,可以拿到線程對象進行詳細設(shè)置
- 優(yōu)缺點:
- 缺點:需要手動開啟線程執(zhí)行任務(wù)
- 優(yōu)點:可以拿到線程對象
// 創(chuàng)建線程
/**
* 參數(shù)一:目標對象
* 參數(shù)二:方法選擇器(線程啟動后調(diào)用的方法)
* 參數(shù)三:調(diào)用方法需要接受的參數(shù)
*/
NSThread *thread = [[NSThread alloc] initWithTarget:self
selector:@selector(task)
object:nil];
// 開始執(zhí)行
[thread start];
- 第二種(分離出一條子線程)
- 特點:自動啟動線程,無法對線程進行更詳細的設(shè)置
- 優(yōu)缺點:
- 缺點:無法拿到線程對象 進行更詳細設(shè)置
- 優(yōu)點:代碼簡單且自動開啟線程執(zhí)行
// 創(chuàng)建線程
/**
* 參數(shù)一:要調(diào)用的方法
* 參數(shù)二:目標對象 self
* 參數(shù)三:調(diào)用方法需傳遞的參數(shù)
*/
[NSThread detachNewThreadSelector:@selector(task)
toTarget:self
withObject:nil];
- 第三種(后臺線程)
- 特點:自動啟動線程,無法進行更詳細設(shè)置
- 優(yōu)缺點:
- 缺點:無法拿到線程對象 進行更詳細設(shè)置
- 優(yōu)點:代碼簡單且自動開啟線程執(zhí)行
/**
* NSThread創(chuàng)建一條后臺線程
*/
- (void)nsthreadTest3
{
// 創(chuàng)建線程
/**
* 參數(shù)一:要調(diào)用的方法
* 參數(shù)二:調(diào)用方法需傳遞的參數(shù)
*/
[self performSelectorInBackground:@selector(run:) withObject:@"后臺線程"];
}
- (void)run:(NSString *)str
{
NSLog(@"當(dāng)前線程:%@ -- 接收到的參數(shù):%@", [NSThread currentThread], str);
}
- 第四種(自定義NSThread類并重寫內(nèi)部的方法實現(xiàn))
- 特點:可以不暴露一些實現(xiàn)細節(jié),使代碼增加隱蔽性。(一般出現(xiàn)在第三方框架內(nèi))
- 優(yōu)缺點:
- 缺點:繁瑣,且需要手動開啟線程執(zhí)行
- 優(yōu)點:增加代碼隱蔽性
1.創(chuàng)建自定義類繼承自NSThread
2.重寫NSThread類中的main
方法
- (void)main
{
NSLog(@"當(dāng)前線程--%@", [NSThread currentThread]);
}
3.創(chuàng)建線程對象
/**
* NSThread創(chuàng)建一條后臺線程
*/
- (void)nsthreadTest4
{
// 創(chuàng)建線程
SJThread *thread = [[SJThread alloc] init];
// 開啟執(zhí)行
[thread start];
}
線程間通信
有時候我們會從服務(wù)器上下載圖片然后再展示出來,下載的操作我們會放到子線程,而UI刷新的操作只能在主線程中執(zhí)行
。這樣就涉及到線程間的通信
。接下來我們分三種方式來簡單實現(xiàn)一下:
- 方式一:
- (void)viewDidLoad {
[super viewDidLoad];
// 開啟一條線程下載圖片
[NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
}
- (void)downloadImage
{
// 網(wǎng)絡(luò)圖片url
NSURL *url = [NSURL URLWithString:@"http://img3.imgtn.bdimg.com/it/u=3841157212,2135341815&fm=206&gp=0.jpg"];
// 根據(jù)url下載圖片數(shù)據(jù)到本地
NSData *imageData = [NSData dataWithContentsOfURL:url];
// 把下載到本地的二進制數(shù)據(jù)轉(zhuǎn)成圖片
UIImage *image = [UIImage imageWithData:imageData];
// 回到主線程刷新UI
// 第一種方式
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
// 第二種方式
// 直接調(diào)用iconView里面的setImage:方法就可以實現(xiàn)刷新
// [self.iconView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
// 第三種方式
// 此方法可以方便自由在主線程和其它線程切換
// [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
- (void)showImage:(UIImage *)image
{
self.iconView.image = image;
}
GCD簡單使用
什么是GCD
- GCD全稱是Grand Central Dispatch(牛逼的中樞調(diào)度器)
- 純C語言,提供了非常多強大的函數(shù)
GCD優(yōu)勢
- GCD是蘋果公司為多核的并行運算提出的解決方案
- GCD會自動利用更多的CPU內(nèi)核
- GCD會自動關(guān)了線程生命周期(創(chuàng)建、調(diào)度、銷毀線程)
- GCD性能很好(接近底層)
GCD的組合方式
- 異步函數(shù)+并發(fā)隊列:開啟多條線程,并發(fā)執(zhí)行任務(wù)
- 異步函數(shù)+串行隊列:開啟一條線程,串行執(zhí)行任務(wù)
- 同步函數(shù)+并發(fā)隊列:不開線程,串行執(zhí)行任務(wù)
- 同步函數(shù)+串行隊列:不開線程,串行執(zhí)行任務(wù)
- 異步函數(shù)+主隊列:不開線程,在主線程中串行執(zhí)行任務(wù)
- 同步函數(shù)+主隊列:不開線程,串行執(zhí)行任務(wù)(注意死鎖發(fā)生
注意同步函數(shù)和異步函數(shù)在執(zhí)行順序上面的差異
GCD的任務(wù)和隊列
- 任務(wù):執(zhí)行什么操作
- 隊列:用來存放任務(wù)(GCD中提供了2種隊列)
- 串行隊列
- 并發(fā)隊列
GCD的使用
- 定制任務(wù) —— 確定需要做的操作
- 將任務(wù)添加到隊列中
- GCD會自動將隊列中的任務(wù)取出,存放到線程中執(zhí)行
- 任務(wù)的取出遵循隊列的FIFO原則(先進先出,后進后出)
GCD創(chuàng)建線程
- 接下來看看同步函數(shù)和異步函數(shù)有什么區(qū)別:
1.先來看看異步并發(fā)隊列
- (void)test
{
/**
* 參數(shù)一:C語言的字符串,給隊列起一個名字或標識
* 參數(shù)二:隊列類型
DISPATCH_QUEUE_CONCURRENT 并發(fā)
DISPATCH_QUEUE_SERIAL 串行
*/
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
/**
* 使用函數(shù)封裝任務(wù)
* 參數(shù)一:獲取隊列
* 參數(shù)二:需要執(zhí)行的任務(wù)
*/
dispatch_async(queue, ^{
NSLog(@"在:%@線程執(zhí)行了任務(wù)",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
}
執(zhí)行結(jié)果:
2.再來看看同步并發(fā)隊列
- (void)test
{
/**
* 參數(shù)一:C語言的字符串,給隊列起一個名字或標識
* 參數(shù)二:隊列類型
DISPATCH_QUEUE_CONCURRENT 并發(fā)
DISPATCH_QUEUE_SERIAL 串行(串行隊列可以用NULL表示)
*/
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
/**
* 使用函數(shù)封裝任務(wù)
* 參數(shù)一:獲取隊列
* 參數(shù)二:需要執(zhí)行的任務(wù)
*/
dispatch_sync(queue, ^{
NSLog(@"在:%@線程執(zhí)行了任務(wù)",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
}
執(zhí)行結(jié)果:
結(jié)論:
從上面的2個運行結(jié)果的時間可以看出
1.異步并發(fā)隊列,會開啟一條子線程來處理任務(wù),以達到主線程和子線程同時執(zhí)行的并發(fā)效果。
2.同步并發(fā)隊列,不會開線程,必須等block塊中的代碼先執(zhí)行完畢才會繼續(xù)執(zhí)行以外的任務(wù),所以并發(fā)隊列對于同步函數(shù)來說等同于“無效”
- 再看看并發(fā)隊列對異步函數(shù)和同步函數(shù)的影響:
1.同步函數(shù)+并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
執(zhí)行結(jié)果:同步函數(shù)+并發(fā)隊列沒有開啟子線程的能力
2.異步函數(shù)+并發(fā)隊列
- (void)test2
{
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:異步函數(shù)+并發(fā)隊列會自動開啟3條子線程執(zhí)行任務(wù)
結(jié)論:
從上面可以看出,異步函數(shù)擁有開啟子線程的能力,而同步函數(shù)沒有開啟子線程的能力。
- GCD中,除了并發(fā)隊列外,還有串行隊列,我們來看看如果把并發(fā)隊列換成串行隊列會有怎樣的變化
1.同步函數(shù)+串行隊列
- (void)test2
{
dispatch_queue_t queue = dispatch_queue_create("串行隊列", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:進一步證明同步函數(shù)沒有開啟子線程的能力,他的所有任務(wù)都在主線程中執(zhí)行
2.異步函數(shù)+串行隊列
- (void)test2
{
dispatch_queue_t queue = dispatch_queue_create("串行隊列", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:開啟了一條子線程,在子線程中依次執(zhí)行任務(wù)
結(jié)論
1.在同步函數(shù)+串行隊列中,任務(wù)依舊是在主線程中執(zhí)行。
2.在異步函數(shù)+串行隊列中,會自動開啟一條子線程,在子線程中依次執(zhí)行任務(wù)
3.再一次證明同步函數(shù)沒有開啟子線程的能力
系統(tǒng)提供的4個全局并發(fā)隊列
- 在iOS中系統(tǒng)默認給我們提供了4個全局并發(fā)隊列
- (void)test3
{
// 獲取全局并發(fā)隊列
// 系統(tǒng)內(nèi)部默認提供4個全局并發(fā)隊列
/**
* 參數(shù)一:優(yōu)先級
* 參數(shù)二:時間(傳0即可)
*/
//優(yōu)先級:DISPATCH_QUEUE_PRIORITY_HIGH 2
// DISPATCH_QUEUE_PRIORITY_DEFAULT 0
// DISPATCH_QUEUE_PRIORITY_LOW (-2)
// DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 級別最低
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"1當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"5當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"6當(dāng)前線程:%@", [NSThread currentThread]);
});
}
執(zhí)行結(jié)果:在結(jié)果中我們看到GCD創(chuàng)建了6條線程,但是實際上GCD創(chuàng)建多少條線程完全由系統(tǒng)當(dāng)前情況而定,我們是無法控制的。
特殊的串行隊列 —— 主隊列(與主線程相關(guān)聯(lián)的隊列)
- 主隊列是GCD自帶的一種特殊的串行隊列
- 放在主隊列中的人物,都會放到主線程中執(zhí)行
- 使用dispatch_get_main_queue()的方式可獲取主隊列
-
特點
- 1.放在主隊列中的任務(wù),必須在主線程中執(zhí)行
- 2.主隊列執(zhí)行任務(wù)的時候,在調(diào)度任務(wù)的時候,會先調(diào)用主線程的狀態(tài),如果當(dāng)前有任務(wù)在做,則會等待主線程執(zhí)行完任務(wù)再執(zhí)行自己的任務(wù)
-
1.主隊列+異步函數(shù)
- (void)test4
{
// 獲取主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 添加任務(wù)
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
});
}
執(zhí)行結(jié)果:任務(wù)都在主線程中執(zhí)行
2.同步函數(shù)+主隊列
- (void)test4
{
// 獲取主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 添加任務(wù)
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
進入死鎖狀態(tài),因為主隊列執(zhí)行任務(wù)的時候,在調(diào)度任務(wù)的時候,會先調(diào)用主線程的狀態(tài),如果當(dāng)前有任務(wù)在做,則會等待主線程執(zhí)行完任務(wù)再執(zhí)行自己的任務(wù)
如果要解決以上的情況,那么可以將任務(wù)添加到子線程中,這樣就不會出現(xiàn)死鎖的情況,程序也就能夠正常執(zhí)行了
[self performSelectorInBackground:@selector(test4) withObject:nil];
- (void)test4
{
// 獲取主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 添加任務(wù)
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
總結(jié)
函數(shù)類型 | 并發(fā)隊列 | 手動創(chuàng)建的串行隊列 | 主隊列 |
---|---|---|---|
同步 (sync) | 1.沒有開啟新線程 2.串行執(zhí)行任務(wù) |
1.有開啟新線程 2.串行執(zhí)行任務(wù) |
死鎖 |
異步(async) | 1.有開啟新線程 2.并發(fā)執(zhí)行任務(wù) |
1.有開啟新線程 2.串行執(zhí)行任務(wù) |
1.沒有開啟新線程 2.串行執(zhí)行任務(wù) |
注意
使用sync函數(shù)往當(dāng)前串行隊列中添加任務(wù),會卡主當(dāng)前的串行隊列。
GCD線程間的通信
- 有時候我們需要在子線程進行一些耗時操作,等耗時操作完成后再回到主線程進行相應(yīng)的UI刷新,那么就可以使用下面的方式在子線程和主線程之間進行通信
- (void)test5
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"在%@線程中執(zhí)行任務(wù)", [NSThread currentThread]);
// 回到主線程
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"在%@線程中執(zhí)行任務(wù)", [NSThread currentThread]);
});
});
}
執(zhí)行結(jié)果:
GCD延遲執(zhí)行
- 特點:可以選擇在哪個線程中執(zhí)行任務(wù)
- (void)test6
{
NSLog(@"方法開始運行");
/**
* GCD延遲執(zhí)行方法
*
* 參數(shù)一: 要延遲的時間 (以秒為單位)
* 參數(shù)二: 在哪個線程中執(zhí)行
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"GCD定時器");
});
}
執(zhí)行結(jié)果:
一次性代碼
- 特點:
- 能保證整個程序運行過程中,block內(nèi)的代碼塊只會被執(zhí)行一次
- 線程是安全的
- 應(yīng)用:
簡單的
單例模式(單例模式實現(xiàn)點我) - 注意點:不可放在懶加載中
- (void)test8
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"一次性代碼運行");
});
}
柵欄函數(shù)
- 作用:能夠控制并發(fā)隊列里面任務(wù)的執(zhí)行順序
- 注意:不能使用全局并發(fā)隊列(會沒有任何區(qū)別,文檔中有注釋——只對自己創(chuàng)建的并發(fā)隊列有效)
- (void)test7
{
// 創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_create(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i<5; i++) {
NSLog(@"1");
}
});
dispatch_async(queue, ^{
for (int i = 0; i<5; i++) {
NSLog(@"2");
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"進入柵欄函數(shù)");
});
dispatch_async(queue, ^{
for (int i = 0; i<5; i++) {
NSLog(@"3");
}
});
dispatch_async(queue, ^{
for (int i = 0; i<5; i++) {
NSLog(@"4");
}
});
}
執(zhí)行結(jié)果:
GCD迭代開發(fā)(遍歷)
- 一般我們傳統(tǒng)的遍歷方式如下,它的缺點就是在處理比較耗時的操作時效率較低,因為只在一個線程中執(zhí)行任務(wù)。
// 傳統(tǒng)的遍歷方式
for (int i ; i< 10; i++) {
NSLog(@"%d -- 當(dāng)前線程%@", i, [NSThread currentThread]);
}
執(zhí)行結(jié)果:
- 在GCD中,為我們提供了一個迭代函數(shù),可以開啟子線程快速進行遍歷,這樣就可以大大提高效率,而且使用非常簡單。接下來使用迭代函數(shù)來進行文件復(fù)制的操作:
- (void)test9
{
// 獲得文件原始路徑(上層文件夾得路徑)
NSString *fromPath = @"/Users/yeshaojian/Desktop/test";
// 獲得文件的目標路徑
NSString *toPath = @"/Users/yeshaojian/Desktop/test2";
// 得到文件路徑下面的所有文件
NSArray *subpaths = [[NSFileManager defaultManager] subpathsAtPath:fromPath];
NSLog(@"文件名:%@",subpaths);
// 獲取數(shù)組中文件的個數(shù)
NSInteger count = subpaths.count;
// 將要迭代的操作放到迭代函數(shù)內(nèi)
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index){
// 拼接需要復(fù)制的文件的全路徑
NSString *fromFullpath = [fromPath stringByAppendingPathComponent:subpaths[index]];
// 拼接目標目錄的全路徑
NSString *toFullpath = [toPath stringByAppendingPathComponent:subpaths[index]];
// 執(zhí)行文件剪切操作
/*
* 參數(shù)一:文件在哪里的全路徑
* 參數(shù)二:文件要被剪切到哪里的全路徑
*/
[[NSFileManager defaultManager] moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
NSLog(@"拼接需要復(fù)制的文件的全路徑:%@ -- 拼接目標目錄的全路徑:%@ -- 當(dāng)前線程:%@",fromFullpath,toFullpath,[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
隊列組
- 假如開發(fā)中有多個任務(wù),要求在所有任務(wù)都在子線程中并發(fā)執(zhí)行,且不能使用柵欄函數(shù),當(dāng)所有任務(wù)都執(zhí)行完成后打印“完成”。這樣的需求就需要用到GCD中的隊列組。
- 應(yīng)用場合:
- 對多個任務(wù)有強制依賴性,缺一不可時使用
1.隊列組的基本使用
- (void)test10
{
// 獲取隊列組,用來管理隊列
dispatch_group_t group = dispatch_group_create();
// 獲取并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("cs", DISPATCH_QUEUE_CONCURRENT);
// 添加任務(wù)
dispatch_group_async(group, queue, ^{
NSLog(@"cs1---%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"cs2---%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"cs3---%@", [NSThread currentThread]);
});
// 攔截通知:當(dāng)隊列組中所有的任務(wù)都執(zhí)行完畢后,會調(diào)用下面方法的block塊
dispatch_group_notify(group, queue, ^{
NSLog(@"完成");
});
}
執(zhí)行結(jié)果:
隊列組函數(shù)內(nèi)部操作簡要流程
處理流程:
1.封裝任務(wù)
2.把任務(wù)提交到隊列
3.把當(dāng)前任務(wù)的執(zhí)行情況納入到隊列注的監(jiān)聽范圍
注意:下面方法本身是異步的
dispatch_group_notify(group, queue, ^{
});
拓展:
在一些框架或者早期項目中,可能會見到下面2種隊列組的使用方法,在這邊順帶提及一下,但不推薦使用,因為太過繁瑣。
第一種
- (void)test11
{
// 獲得隊列組,管理隊列
dispatch_group_t group = dispatch_group_create();
// 獲得并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
// 表示開始把后面的異步任務(wù)納入到監(jiān)聽范圍
//dispatch_group_enter & dispatch_group_leave
dispatch_group_enter(group);
// 使用異步函數(shù)封裝任務(wù)
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
// 通知隊列組該任務(wù)已經(jīng)執(zhí)行完畢
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
// 攔截通知
dispatch_group_notify(group, queue, ^{
NSLog(@"--完成---");
});
}
第二種
- (void)test11
{
// 獲得隊列組,管理隊列
dispatch_group_t group = dispatch_group_create();
// 獲得并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
// 表示開始把后面的異步任務(wù)納入到監(jiān)聽范圍
//dispatch_group_enter & dispatch_group_leave
dispatch_group_enter(group);
// 使用異步函數(shù)封裝任務(wù)
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
// 通知隊列組該任務(wù)已經(jīng)執(zhí)行完畢
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
// 等待DISPATCH_TIME_FOREVER 死等,一直要等到所有的任務(wù)都執(zhí)行完畢之后才會繼續(xù)往下執(zhí)行
// 同步執(zhí)行
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 0.00001 * NSEC_PER_SEC);
// 等待timer m的時間 不管隊列中的任務(wù)有沒有執(zhí)行完畢都繼續(xù)往下執(zhí)行,如果在該時間內(nèi)所有事任務(wù)都執(zhí)行完畢了那么會返回一個0,否則是非0值
long n = dispatch_group_wait(group, timer);
NSLog(@"%ld",n);
NSLog(@"--完成---");
}
補充:同步\異步函數(shù)另一種創(chuàng)建方式
- 其實同步函數(shù)和異步函數(shù)還有另外的創(chuàng)建方式,但是使用起來比較不方便,所以上面就沒提及,想想還是補充一下好了
1.異步函數(shù)(創(chuàng)建一個使用函數(shù)封裝代碼的異步函數(shù))
- (void)test12
{
/**
* 參數(shù)一:隊列
* 參數(shù)二:要傳給函數(shù)的參數(shù)
* 參數(shù)三:函數(shù)
*/
dispatch_async_f(dispatch_get_global_queue(0, 0), NULL, testTask);
}
void testTask(void *param)
{
NSLog(@"%@", [NSThread currentThread]);
}
2.同步函數(shù)(創(chuàng)建一個使用函數(shù)封裝代碼的同步函數(shù))
- (void)test12
{
/**
* 參數(shù)一:隊列
* 參數(shù)二:要傳給函數(shù)的參數(shù)
* 參數(shù)三:函數(shù)
*/
dispatch_sync_f(dispatch_get_global_queue(0, 0), NULL, testTask);
}
void testTask(void *param)
{
NSLog(@"%@", [NSThread currentThread]);
}
上面使用的是函數(shù)來封裝要處理的代碼,使用比較不方便,且block是輕量級的數(shù)據(jù)結(jié)構(gòu),更推薦使用block封裝代碼的形式創(chuàng)建同步\異步函數(shù)。
GCD一些需要注意的細節(jié)
- 全局并發(fā)隊列是默認存在的(在我們程序運行的時候就存在)
- 全局隊列根據(jù)隊列的優(yōu)先級分為 (高,默認,低,后臺優(yōu)先級)4個并發(fā)隊列
- iOS 6之前,我們通過創(chuàng)建的線程,是要自己手動施放的
- 施放的方式 —— dispatch_release()
- 使用柵欄函數(shù),蘋果官方文檔明確規(guī)定柵欄函數(shù)只有在和使用create函數(shù)創(chuàng)建的筆法隊列一起使用才有效
- 暫時就想到這么多O(∩_∩)O,因為GCD已經(jīng)開源,想研究的朋友可以到網(wǎng)上搜索一下,有哪里不對的可以聯(lián)系我,謝謝!
NSOperation簡單使用
NSOperation作用
- 配合使用NSOperation和NSOperationQueue也能實現(xiàn)多線程編程
NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟
- 先將需要執(zhí)行的操作封裝到一個NSOperation對象中
- 然后將NSOperation對象添加到NSOperation對象中
- 系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來
- 將取出來的NSOperation封裝的操作放到一條新線程中執(zhí)行
NSOperation的子類
- NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類
- 使用NSOperation子類的方式有3種
- NSInvocationOperation
- NSBlockOperation
- 自定義子類繼承NSOperation,實現(xiàn)內(nèi)部相應(yīng)的方法
NSOperation封裝操作
- 第一種方式 —— NSInvocationOperation
- (void)invocationTest
{
/**
* 參數(shù)一:目標對象
* 參數(shù)二:調(diào)用方法
*/
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
// 開啟任務(wù)
[op1 start];
}
- (void)download
{
NSLog(@"下載:%@",[NSThread currentThread]);
}
執(zhí)行結(jié)果:需要和隊列并用才會開啟子線程執(zhí)行任務(wù)
- 第二種方式 —— Block
- (void)blockTest
{
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"增加的下載:%@", [NSThread currentThread]);
}];
// 開啟任務(wù)
[op1 start];
[op2 start];
[op3 start];
}
- (void)download
{
NSLog(@"下載:%@",[NSThread currentThread]);
}
執(zhí)行結(jié)果:如果一條線程中執(zhí)行的操作大于1就會開啟新線程并發(fā)執(zhí)行
- 方式三 —— 自定義NSOperation
1.先創(chuàng)建一個繼承自NSOperation的類并重寫main方法
- (void)main
{
NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
}
2.在需要使用的類中引用自定義的類,并創(chuàng)建開啟任務(wù)
- (void)custom
{
SJOperation *op1 = [[SJOperation alloc] init];
[op1 start];
}
執(zhí)行結(jié)果:需要手動開啟線程或者與隊列并用才會開啟子線程
NSOperation中的隊列
- 主隊列 (獲取方式:+mainQueue)
- 所有在主隊列中的任務(wù)都在主線程中執(zhí)行
- 本質(zhì)上是串行隊列
- (void)invocationQueue
{
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download3) object:nil];
// 獲取主隊列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
執(zhí)行結(jié)果:所有任務(wù)都在主隊列中執(zhí)行,且是串行隊列
- 非主隊列(獲取方式:alloc init)
- 同時具備并發(fā)和串行功能
- 默認下是并發(fā)的
- (void)invocationQueue
{
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download3) object:nil];
// 獲取非主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
執(zhí)行結(jié)果:所有任務(wù)在子線程中并發(fā)執(zhí)行
注意:addOperation:內(nèi)部已經(jīng)幫我們執(zhí)行了開啟任務(wù)方法,所有不需要另外實現(xiàn)。
NSBlockOperation與隊列并用的簡單寫法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
執(zhí)行結(jié)果:所有任務(wù)都在子線程中并發(fā)執(zhí)行
設(shè)置最大并發(fā)數(shù)
- 在NSOperation中,我們要想控制串行隊列或者并發(fā)隊列,只需要設(shè)置
maxConcurrentOperationCount
屬性即可- 一般我們要使用串行隊列,只需設(shè)置值為1即可
- 如果值大于1,則為并發(fā)隊列
1.串行隊列示例
- (void)blockQueue
{
// 創(chuàng)建非主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 設(shè)置最大并發(fā)數(shù)為1,則隊列為串行隊列
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
NSLog(@"下載1:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載2:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載3:%@",[NSThread currentThread]);
}];
}
執(zhí)行結(jié)果:按照任務(wù)添加順序執(zhí)行,所以是串行隊列
2.并發(fā)隊列示例
- (void)blockQueue
{
// 創(chuàng)建非主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 設(shè)置最大并發(fā)數(shù)為6,一般子線程控制在6以內(nèi),太多線程會使設(shè)備壓力過大
queue.maxConcurrentOperationCount = 6;
[queue addOperationWithBlock:^{
NSLog(@"下載1:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載2:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載3:%@",[NSThread currentThread]);
}];
}
執(zhí)行結(jié)果:程序并沒有按照添加順序完成任務(wù),所以是并發(fā)執(zhí)行
注意:
- 一般子線程控制在6以內(nèi),太多線程會使設(shè)備壓力過大
-
maxConcurrentOperationCount
默認值為-1(在計算機中,-1一般指最大值) - 如果將
maxConcurrentOperationCount
設(shè)置為0,說明同一時間內(nèi)執(zhí)行0個任務(wù),所以任務(wù)將不會執(zhí)行。
NSOperation暫停、恢復(fù)和取消功能
- 在NSOperation中,已經(jīng)為我們提供了暫停、恢復(fù)和取消的功能,我們只需調(diào)用相應(yīng)的方法即可。
1.暫停
// 暫停
[queue setSuspended:YES];
2.恢復(fù)
// 取消
[queue setSuspended:NO];
3.取消
// 取消隊列中所有操作,且取消后的任務(wù)不可恢復(fù)
[queue cancelAllOperations];
注意:
1.隊列中的的任務(wù)是有狀態(tài)的,分別是 —— 等待;執(zhí)行;完成三種狀態(tài),且暫停、恢復(fù)和取消操作并不能作用于當(dāng)前正處于執(zhí)行狀態(tài)的任務(wù),只能作用于等待狀態(tài)的任務(wù)。
2.如果是自定義的NSOperation,會發(fā)現(xiàn)暫停、恢復(fù)操作對其無效,對于這種情況,可以用以下方式解決 —— 使用取消操作
- (void)main
{
// 模擬耗時操作
for (int i = 0; i< 200; i++) {
NSLog(@"1當(dāng)前線程:%@", [NSThread currentThread]);
}
// 判斷當(dāng)前狀態(tài),如果已經(jīng)取消,直接返回
if (self.cancelled) return;
// 模擬耗時操作
for (int i = 0; i< 200; i++) {
NSLog(@"2當(dāng)前線程:%@", [NSThread currentThread]);
}
// 判斷當(dāng)前狀態(tài),如果已經(jīng)取消,直接返回
if (self.cancelled) return;
// 模擬耗時操作
for (int i = 0; i< 200; i++) {
NSLog(@"3當(dāng)前線程:%@", [NSThread currentThread]);
}
// 判斷當(dāng)前狀態(tài),如果已經(jīng)取消,直接返回
if (self.cancelled) return;
}
解決問題思路:其實這是蘋果官方文檔中的建議 —— 因為,當(dāng)我們調(diào)用cancelAllOperations:
方法的時候,他內(nèi)部的cancelled
屬性就會為真,每執(zhí)行完一個耗時操作后都進行一次判斷,如果發(fā)現(xiàn)已經(jīng)取消,則退出執(zhí)行
。如果想更精確操控的話,也可以將判斷操作放到耗時操作中,但是不建議這樣做,因為這樣性能極差。
NSOperation中的依賴操作
- NSOperation提供了一套非常便捷好用的操作依賴方式,比起GCD,那種酸爽簡直不敢相信
- (void)blockQueue
{
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載1:%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載2:%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載3:%@",[NSThread currentThread]);
}];
// 設(shè)置依賴關(guān)系
// op1依賴op2,只有當(dāng)op2執(zhí)行完畢后,才會執(zhí)行op1
[op1 addDependency:op2];
// op2依賴op3,只有當(dāng)op3執(zhí)行完畢后,才會執(zhí)行op2
[op2 addDependency:op3];
// 獲取主隊列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
執(zhí)行結(jié)果:先執(zhí)行完op3,等op3執(zhí)行完成后才執(zhí)行op2,當(dāng)op2執(zhí)行完畢后,才執(zhí)行op1
注意
- NSOperation提供的操作依賴功能特別強大,可以設(shè)置不同隊列的依賴
- 但是不能循環(huán)依賴,比如op1依賴op2,op2又依賴op1,而且并不會報錯,但會發(fā)生死鎖,且有關(guān)任務(wù)都不執(zhí)行。
NSOperation的監(jiān)聽
- 我們經(jīng)常有這樣的需要:在某些任務(wù)執(zhí)行完成后,再執(zhí)行指定的某些操作,那么NSOperation中的監(jiān)聽功能就派上用場了,使用非常簡單
NSOperation *op = [[NSOperation alloc] init];
op.completionBlock = ^{
NSLog(@"下載完成");
};
[op start];
NSOperation線程間通信
- NSOperation線程間的通信類似于GCD,所以就不多敘述了,直接上代碼
- (void)downloadPhoto
{
// 獲取非主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建下載任務(wù)
[queue addOperationWithBlock:^{
// 圖片地址
NSURL *url = [NSURL URLWithString:@"http://cdn.duitang.com/uploads/item/201512/05/20151205092106_aksZU.jpeg"];
// 下載圖片
NSData *imageData = [NSData dataWithContentsOfURL:url];
// 轉(zhuǎn)換圖片
UIImage *image = [UIImage imageWithData:imageData];
// 回到主線程刷新
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"回%@線程刷新UI", [NSThread currentThread]);
self.imageView.image = image;
}];
}];
}
執(zhí)行結(jié)果:
GCD和NSOperation區(qū)別
在開發(fā)中最常用的就是GCD和NSOperation來進行多線程開發(fā),NSThread更多是在測試時輔助使用,pthread則很少看見,這里為大家簡單整理一下他們之間的區(qū)別
-
GCD和NSOperation的對比
- GCD是純C語言的API,而操作隊列(NSOperation)則是Object-C的對象
- 在GCD中,任務(wù)用Block塊來表示,而塊是輕量級的數(shù)據(jù)結(jié)構(gòu),相反,操作隊列(NSOperation)中的
操作
NSOperation是比較重量級的Object-C對象
-
那么在開發(fā)中如何選擇呢?
- 一般如果任務(wù)之間有依賴關(guān)系或者需要監(jiān)聽任務(wù)執(zhí)行的過程(KVO),首選NSOperation
- 單純進行一些耗時操作則選用GCD,因為相比NSOperation,GCD效率更高,性能更好
-
NSOperation和NSOperationQueue好處
- NSOperation可以方便設(shè)置操作優(yōu)先級(表示操作在隊列中與其它操作之間的優(yōu)先關(guān)系,級別越高越先執(zhí)行)
- NSOperation可以通過KVO的方式對NSOperation對象進行監(jiān)聽控制(監(jiān)聽當(dāng)前操作是處于完成,取消還是執(zhí)行狀態(tài))
- NSOperation可以方便設(shè)置操作之間的依賴關(guān)系
- 通過自定義NSOperation子類可以實現(xiàn)操作復(fù)用