前言
最近一直在做項目,也是經(jīng)常有用到一些線程的知識俄烁,抽出一些時間來對線程做一下匯總页屠,由淺入深蓖柔,也很方便新手入門况鸣。
概述
多線程開發(fā)在iOS中有著舉足輕重的位置镐捧,學(xué)習(xí)好多線程是每一個iOS Developer必須要掌握的技能。
概念
進(jìn)程
進(jìn)程代表當(dāng)前運行的一個程序
是系統(tǒng)分配資源的基本單位
每個進(jìn)程之間是獨立的速址,每個進(jìn)程均運行在其專用且受保護(hù)的內(nèi)存空間內(nèi)
比如同時打開QQ芍锚、Xcode并炮,系統(tǒng)就會分別啟動2個進(jìn)程
- 進(jìn)程可以理解為一個工廠
- 通過“活動監(jiān)視器”可以查看Mac系統(tǒng)中所開啟的進(jìn)程
線程
線程是進(jìn)程的基本執(zhí)行單元逃魄,一個進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行
一個進(jìn)程含有一個線程或多個線程
應(yīng)用程序打開后會默認(rèn)開辟一個線程叫做主線程或者UI線程
比如使用酷狗播放音樂伍俘、使用迅雷下載電影勉躺,都需要在線程中執(zhí)行
- 線程可以理解為工廠里的工人
串行
多個任務(wù)按順序執(zhí)行
類似于一個窗口辦公排隊
也就是說饵溅,在同一時間內(nèi),1個線程只能執(zhí)行1個任務(wù)
比如在1個線程中下載3個文件(分別是文件A蜕企、文件B幸乒、文件C)就要依次執(zhí)行
并行
多個任務(wù)同一時間一起執(zhí)行
類似于多個窗口辦公
比如同時開啟3條線程分別下載3個文件(分別是文件A逝变、文件B、文件C)弥臼,同時執(zhí)行
并發(fā)
很多人容易認(rèn)為并發(fā)和并行是一個意思径缅,但實際上他們有本質(zhì)的區(qū)別
并發(fā)看起來像多個任務(wù)同一時間一起執(zhí)行
但實際上是CPU快速的輪轉(zhuǎn)切換造成的假象
多線程
-
本質(zhì)
- 在一個進(jìn)程中開啟多個線程并發(fā)執(zhí)行
-
原理
同一時間纳猪,CPU只能處理1條線程氏堤,只有1條線程在工作(執(zhí)行)
多線程并發(fā)(同時)執(zhí)行鼠锈,其實是CPU快速地在多條線程之間調(diào)度(切換)
如果CPU調(diào)度線程的時間足夠快购笆,就造成了多線程并發(fā)執(zhí)行的假象
-
優(yōu)點
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源利用率(CPU样傍、內(nèi)存利用率)
-
缺點
線程需要耗費系統(tǒng)資源
主線程需要消耗椘趟欤空間的1MB資源
其他線程每個消耗512KB資源
程序設(shè)計更加復(fù)雜:比如線程之間的通信娃循、多線程的數(shù)據(jù)共享
不推薦過多使用
主線程
- 概念
- 一個iOS程序運行后捌斧,默認(rèn)會開啟1條線程捞蚂,稱為“主線程”或“UI線程”
- 作用
顯示\刷新UI界面
處理UI事件(比如點擊事件、滾動事件敲霍、拖拽事件等)
- 注意
別將比較耗時的操作放到主線程中
耗時操作會卡住主線程肩杈,嚴(yán)重影響UI的流暢度扩然,給用戶一種“卡”的壞體驗
耗時操作執(zhí)行
- 如果放在主線程
因為在主線程中的任務(wù)是按照順序依次執(zhí)行的
如果把耗時操作放在主線程里夫偶,會等待它執(zhí)行完后才能執(zhí)行其他操作
如果在等待執(zhí)行完畢的時間里點擊了其他控件就會給用戶一種卡住的感覺兵拢,嚴(yán)重影響用戶體驗
- 如果放在子線程
在用戶點擊按鈕的時候就會做出反應(yīng)
兩個線程同時執(zhí)行说铃,互不影響
多線程的實現(xiàn)
方案
PThread
- 簡單了解即可
- (IBAction)buttonClick:(id)sender {
pthread_t thread;
pthread_create(&thread, NULL, run, NULL);
pthread_t thread2;
pthread_create(&thread2, NULL, run, NULL);
}
void * run(void *param)
{
for (NSInteger i = 0; i<50000; i++) {
NSLog(@"------buttonClick---%zd--%@", i, [NSThread currentThread]);
}
return NULL;
}
NSThread
基本創(chuàng)建方法
一個NSThread對象就代表一條線程
創(chuàng)建截汪、啟動線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction) object:nil];
// 需要手動開啟線程
[thread start];
- 主線程相關(guān)用法
// 獲得主線程
+ (NSThread *)mainThread;
// 是否為主線程
- (BOOL)isMainThread;
// 是否為主線程
+ (BOOL)isMainThread;
- 獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];
- 線程的名字
- (void)setName:(NSString *)name;
- (NSString *)name;
-
其他創(chuàng)建方法
- 創(chuàng)建線程后自動啟動線程
[NSThread detachNewThreadSelector:@selector(threadAction)toTarget:self withObject:nil]
- 隱式創(chuàng)建并啟動線程
[self performSelectorInBackground:@selector(threadAction) withObject:nil];
- 上述2種創(chuàng)建線程方式的優(yōu)缺點
- 優(yōu)點:簡單快捷
- 缺點:無法對線程進(jìn)行更詳細(xì)的設(shè)置
- 線程睡眠
[NSThread sleepForTimeInterval:2]; // 讓線程睡眠2秒(阻塞2秒)
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
- 退出當(dāng)前線程
[NSThread exit];
- 設(shè)置線程優(yōu)先級 (默認(rèn)0.5)
thread.threadPriority = 1.0f;
####GCD
- 這里寫了關(guān)于GCD的詳細(xì)介紹疾牲,包括GCD死鎖等問題。
- http://www.reibang.com/p/f13c4b336d34
#### NSOperation
- NSOperation 是蘋果公司對 GCD 的封裝衙解,完全面向?qū)ο笱羧幔允褂闷饋砀美斫狻?大家可以看到 NSOperation 和 NSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊列 。操作步驟也很好理解蚓峦。
- NSOperation也有兩個概念舌剂,隊列和任務(wù)。
- 主隊列
- [NSOperationQueue mainQueue]
- 凡是添加到主隊列中的任務(wù)(NSOperation)暑椰,都會放到主線程中執(zhí)行
- 非主隊列(其他隊列)
- [[NSOperationQueue alloc] init]
- 同時包含了:串行霍转、并發(fā)功能
- 添加到這種隊列中的任務(wù)(NSOperation)避消,就會自動放到子線程中執(zhí)行
- 系統(tǒng)為我們提供了NSOperation的子類我們可以直接使用
- 當(dāng)某個任務(wù)經(jīng)常使用,我們可以自定義NSOperation,在這個NSOperation中的main方法中寫任務(wù)。
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationAction) object:nil];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"haha ----- %@", [NSThread currentThread]);
}];
// 隊列
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
// 任務(wù)1完事以后才執(zhí)行任務(wù)2
[operation2 addDependency:operation1];
// 設(shè)置最大并發(fā)數(shù)()
operationQueue.maxConcurrentOperationCount = 4;
[operationQueue addOperationWithBlock:^{
NSLog(@"hello ------ %@", [NSThread currentThread]);
}];
[operationQueue addOperation:operation1];
[operationQueue addOperation:operation2];
#### NSOperation 對比 GCD
- GCD效率更高,使用起來也很方便
- NSOperation面向?qū)ο螅勺x性更高,架構(gòu)更清晰,對于復(fù)雜多線程場景旁瘫,如并發(fā)中存在串行宁仔,和設(shè)置最大并發(fā)數(shù),擁有現(xiàn)在的API,使用起來特別簡單
# 線程的狀態(tài)
-----
![](http://upload-images.jianshu.io/upload_images/2595997-c99d3dd0bb01ffe4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#### 控制線程的狀態(tài)
------
- 啟動線程
// 進(jìn)入就緒狀態(tài)->運行狀態(tài)。 當(dāng)線程執(zhí)行完畢自動進(jìn)入死亡狀態(tài)。
- (void)start;
- 阻塞(暫停)線程
// 進(jìn)入阻塞狀態(tài)
- (void)sleepUntilData:(NSDate *)data;
- (void)sleepForTimeInterval:(NSTimeInterval)ti;
- 強制停止?fàn)顟B(tài)
// 進(jìn)入死亡狀態(tài)
- (void)exit;
- 注意
- 一旦線程停止(死亡)了,就不能再次開啟任務(wù)
#### 多線程的安全隱患問題
-------
- 1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源
- 比如多個線程訪問同一個對象、同一個變量、同一個文件
- 當(dāng)多個線程訪問同一塊資源時脯倚,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題
- 比如下面這個例子
![](http://upload-images.jianshu.io/upload_images/2595997-ad279c08b04af078.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 線程A從內(nèi)存中拿出一個Integer類型的值再沧,為17炒瘸;進(jìn)行加1操作后變?yōu)?8隘截,然后返回給內(nèi)存
- 線程B同時從內(nèi)存中拿出一個Integer類型的值统台,為17;進(jìn)行加1操作后變?yōu)?8,然后返回給內(nèi)存
- 出現(xiàn)的問題就是分別在兩個線程中做了加1操作,然而最后的結(jié)果只顯示了一次加1的結(jié)果,出現(xiàn)了數(shù)據(jù)錯亂的問題,正確結(jié)果應(yīng)該是變?yōu)?0
-------
- 解決方案,使用互斥鎖
![](http://upload-images.jianshu.io/upload_images/2595997-7a4df7f138bec881.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 線程A進(jìn)入內(nèi)存讀取值得時候先加一把鎖庐镐,讓外界無法拿到17進(jìn)行修改,等線程A對17做完加1操作后返回給內(nèi)存后,再解鎖.
- 此時如果線程B來內(nèi)存中想要修改17的時候,發(fā)現(xiàn)上了鎖,只能等待線程A做完操作后才能修改值嚷那,而A操作完后此時的值已經(jīng)變成了18队询,B從內(nèi)存中要修改的話,直接從內(nèi)存中拿到的就是18范嘱,開始修改送膳,然后加鎖不讓其他線程進(jìn)來晒奕。改完過后坑律,在解鎖宫屠。方便下一個線程進(jìn)來修改。
#### 互斥鎖
------
- 使用前提
- 多條線程搶奪同一塊資源
- 相關(guān)專業(yè)術(shù)語
- 線程同步
- 互斥鎖使用格式
@synchronized(鎖對象)
{
//需要鎖定的代碼
}
- 注意:
- 鎖定1份代碼只用一把鎖灿渴,用多把鎖是無效的
- 為了保證唯一性,鎖對象一般填self
- 互斥鎖的優(yōu)缺點
- 優(yōu)點:
- 能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
- 缺點
- 需要消耗大量的CPU資源
####線程間通信
-----
- 概念
- 在1個進(jìn)程中,線程往往不是孤立存在的,多個線程之間需要經(jīng)常進(jìn)行通信
- 表現(xiàn)
- 1個線程傳遞數(shù)據(jù)給另1個線程
- 在1個線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另1個線程繼續(xù)執(zhí)行任務(wù)
- 線程間通信常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait;
- 例子
![](http://upload-images.jianshu.io/upload_images/2595997-b62b48d63d07cccf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 在子線程中做耗時的操作船响,比如下載圖片
- 在子線程中操作完后要回到主線程做UI的刷新操作(顯示圖片)
- 另外一種線程通信方法(利用NSPort)
- 如果子線程想要傳數(shù)據(jù)給主線程,主線程就要返回一個Port對象讓子線程去擁有犹褒,子線程通過Port對象對主線程進(jìn)行操作。
![](http://upload-images.jianshu.io/upload_images/2595997-2e137ee0e5e71421.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
# 心靈雞湯
------
禪師問:你覺得是一錠金子好诅病,還是一堆爛泥好呢埋涧?壯士答:當(dāng)然是金子啊郑叠!禪師笑曰:假如你是一顆種子呢?壯士答:別他媽搞笑了嘁傀,這錠金子我要定了蕾殴,快給我松手,松手啊魂淡!郁稍!
難受的時候摸摸自己的胸靡羡,告訴自己是個漢子,要堅強~
![](http://upload-images.jianshu.io/upload_images/2595997-51f8a282f95b7f8c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)