前言
最近一直在做項目,也是經常有用到一些線程的知識,抽出一些時間來對線程做一下匯總,由淺入深,也很方便新手入門。
概述
多線程開發在iOS中有著舉足輕重的位置,學習好多線程是每一個iOS Developer必須要掌握的技能。
概念
進程
進程代表當前運行的一個程序
是系統分配資源的基本單位
每個進程之間是獨立的,每個進程均運行在其專用且受保護的內存空間內
比如同時打開QQ、Xcode,系統就會分別啟動2個進程
- 進程可以理解為一個工廠
- 通過“活動監視器”可以查看Mac系統中所開啟的進程
線程
線程是進程的基本執行單元,一個進程(程序)的所有任務都在線程中執行
一個進程含有一個線程或多個線程
應用程序打開后會默認開辟一個線程叫做主線程或者UI線程
比如使用酷狗播放音樂、使用迅雷下載電影,都需要在線程中執行
- 線程可以理解為工廠里的工人
串行
多個任務按順序執行
類似于一個窗口辦公排隊
也就是說,在同一時間內,1個線程只能執行1個任務
比如在1個線程中下載3個文件(分別是文件A、文件B、文件C)就要依次執行
并行
多個任務同一時間一起執行
類似于多個窗口辦公
比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B、文件C),同時執行
并發
很多人容易認為并發和并行是一個意思,但實際上他們有本質的區別
并發看起來像多個任務同一時間一起執行
但實際上是CPU快速的輪轉切換造成的假象
多線程
-
本質
- 在一個進程中開啟多個線程并發執行
-
原理
同一時間,CPU只能處理1條線程,只有1條線程在工作(執行)
多線程并發(同時)執行,其實是CPU快速地在多條線程之間調度(切換)
如果CPU調度線程的時間足夠快,就造成了多線程并發執行的假象
-
優點
能適當提高程序的執行效率
能適當提高資源利用率(CPU、內存利用率)
-
缺點
線程需要耗費系統資源
主線程需要消耗棧空間的1MB資源
其他線程每個消耗512KB資源
程序設計更加復雜:比如線程之間的通信、多線程的數據共享
不推薦過多使用
主線程
- 概念
- 一個iOS程序運行后,默認會開啟1條線程,稱為“主線程”或“UI線程”
- 作用
顯示\刷新UI界面
處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
- 注意
別將比較耗時的操作放到主線程中
耗時操作會卡住主線程,嚴重影響UI的流暢度,給用戶一種“卡”的壞體驗
耗時操作執行
- 如果放在主線程
因為在主線程中的任務是按照順序依次執行的
如果把耗時操作放在主線程里,會等待它執行完后才能執行其他操作
如果在等待執行完畢的時間里點擊了其他控件就會給用戶一種卡住的感覺,嚴重影響用戶體驗
- 如果放在子線程
在用戶點擊按鈕的時候就會做出反應
兩個線程同時執行,互不影響
多線程的實現
方案
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
基本創建方法
一個NSThread對象就代表一條線程
創建、啟動線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction) object:nil];
// 需要手動開啟線程
[thread start];
- 主線程相關用法
// 獲得主線程
+ (NSThread *)mainThread;
// 是否為主線程
- (BOOL)isMainThread;
// 是否為主線程
+ (BOOL)isMainThread;
- 獲得當前線程
NSThread *current = [NSThread currentThread];
- 線程的名字
- (void)setName:(NSString *)name;
- (NSString *)name;
-
其他創建方法
- 創建線程后自動啟動線程
[NSThread detachNewThreadSelector:@selector(threadAction)toTarget:self withObject:nil]
- 隱式創建并啟動線程
[self performSelectorInBackground:@selector(threadAction) withObject:nil];
- 上述2種創建線程方式的優缺點
- 優點:簡單快捷
- 缺點:無法對線程進行更詳細的設置
- 線程睡眠
[NSThread sleepForTimeInterval:2]; // 讓線程睡眠2秒(阻塞2秒)
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
- 退出當前線程
[NSThread exit];
- 設置線程優先級 (默認0.5)
thread.threadPriority = 1.0f;
####GCD
- 這里寫了關于GCD的詳細介紹,包括GCD死鎖等問題。
- http://www.lxweimin.com/p/f13c4b336d34
#### NSOperation
- NSOperation 是蘋果公司對 GCD 的封裝,完全面向對象,所以使用起來更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分別對應 GCD 的 任務 和 隊列 。操作步驟也很好理解。
- NSOperation也有兩個概念,隊列和任務。
- 主隊列
- [NSOperationQueue mainQueue]
- 凡是添加到主隊列中的任務(NSOperation),都會放到主線程中執行
- 非主隊列(其他隊列)
- [[NSOperationQueue alloc] init]
- 同時包含了:串行、并發功能
- 添加到這種隊列中的任務(NSOperation),就會自動放到子線程中執行
- 系統為我們提供了NSOperation的子類我們可以直接使用
- 當某個任務經常使用,我們可以自定義NSOperation,在這個NSOperation中的main方法中寫任務。
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationAction) object:nil];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"haha ----- %@", [NSThread currentThread]);
}];
// 隊列
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
// 任務1完事以后才執行任務2
[operation2 addDependency:operation1];
// 設置最大并發數()
operationQueue.maxConcurrentOperationCount = 4;
[operationQueue addOperationWithBlock:^{
NSLog(@"hello ------ %@", [NSThread currentThread]);
}];
[operationQueue addOperation:operation1];
[operationQueue addOperation:operation2];
#### NSOperation 對比 GCD
- GCD效率更高,使用起來也很方便
- NSOperation面向對象,可讀性更高,架構更清晰,對于復雜多線程場景,如并發中存在串行,和設置最大并發數,擁有現在的API,使用起來特別簡單
# 線程的狀態
-----

#### 控制線程的狀態
------
- 啟動線程
// 進入就緒狀態->運行狀態。 當線程執行完畢自動進入死亡狀態。
- (void)start;
- 阻塞(暫停)線程
// 進入阻塞狀態
- (void)sleepUntilData:(NSDate *)data;
- (void)sleepForTimeInterval:(NSTimeInterval)ti;
- 強制停止狀態
// 進入死亡狀態
- (void)exit;
- 注意
- 一旦線程停止(死亡)了,就不能再次開啟任務
#### 多線程的安全隱患問題
-------
- 1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源
- 比如多個線程訪問同一個對象、同一個變量、同一個文件
- 當多個線程訪問同一塊資源時,很容易引發數據錯亂和數據安全問題
- 比如下面這個例子

- 線程A從內存中拿出一個Integer類型的值,為17;進行加1操作后變為18,然后返回給內存
- 線程B同時從內存中拿出一個Integer類型的值,為17;進行加1操作后變為18,然后返回給內存
- 出現的問題就是分別在兩個線程中做了加1操作,然而最后的結果只顯示了一次加1的結果,出現了數據錯亂的問題,正確結果應該是變為20
-------
- 解決方案,使用互斥鎖

- 線程A進入內存讀取值得時候先加一把鎖,讓外界無法拿到17進行修改,等線程A對17做完加1操作后返回給內存后,再解鎖.
- 此時如果線程B來內存中想要修改17的時候,發現上了鎖,只能等待線程A做完操作后才能修改值,而A操作完后此時的值已經變成了18,B從內存中要修改的話,直接從內存中拿到的就是18,開始修改,然后加鎖不讓其他線程進來。改完過后,在解鎖。方便下一個線程進來修改。
#### 互斥鎖
------
- 使用前提
- 多條線程搶奪同一塊資源
- 相關專業術語
- 線程同步
- 互斥鎖使用格式
@synchronized(鎖對象)
{
//需要鎖定的代碼
}
- 注意:
- 鎖定1份代碼只用一把鎖,用多把鎖是無效的
- 為了保證唯一性,鎖對象一般填self
- 互斥鎖的優缺點
- 優點:
- 能有效防止因多線程搶奪資源造成的數據安全問題
- 缺點
- 需要消耗大量的CPU資源
####線程間通信
-----
- 概念
- 在1個進程中,線程往往不是孤立存在的,多個線程之間需要經常進行通信
- 表現
- 1個線程傳遞數據給另1個線程
- 在1個線程中執行完特定任務后,轉到另1個線程繼續執行任務
- 線程間通信常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait;
- 例子

- 在子線程中做耗時的操作,比如下載圖片
- 在子線程中操作完后要回到主線程做UI的刷新操作(顯示圖片)
- 另外一種線程通信方法(利用NSPort)
- 如果子線程想要傳數據給主線程,主線程就要返回一個Port對象讓子線程去擁有,子線程通過Port對象對主線程進行操作。

# 心靈雞湯
------
禪師問:你覺得是一錠金子好,還是一堆爛泥好呢?壯士答:當然是金子啊!禪師笑曰:假如你是一顆種子呢?壯士答:別他媽搞笑了,這錠金子我要定了,快給我松手,松手啊魂淡!!
難受的時候摸摸自己的胸,告訴自己是個漢子,要堅強~
