iOS多線程技術方案 === 目錄 "一、多線程簡介" "1、多線程的由來" "2、耗時操作的模擬試驗" "3、進程和線程" "4、多線程的概念及原理" "5、多線程的優缺點和一個Tip" "6、主線程" "7、技術方案" "二、Pthread" "1、函數" "2、參數和返回值" "3、使用" " ...
iOS多線程技術方案
目錄
一、多線程簡介
1、多線程的由來
2、耗時操作的模擬試驗
3、進程和線程
4、多線程的概念及原理
5、多線程的優缺點和一個Tip
6、主線程
7、技術方案
二、Pthread
---
1、函數
2、參數和返回值
3、使用
三、NSThread
---
1、創建一個新的線程
2、線程的狀態
3、線程的屬性
四、互斥鎖
---
1、訪問共用資源引入問題!
2、互斥鎖介紹
3、互斥鎖原理
4、互斥鎖和自旋鎖
五、GCD
---
1、GCD介紹
2、GCD的兩個核心
3、函數
4、串列隊列和併發隊列
5、主隊列
6、全局隊列
7、GCD總結
六、NSOperation
---
1、NSOperation簡介
2、核心概念
3、操作步驟
4、NSInvocationOperation
5、NSBlockOperation
七、案例
---
***
一、多線程簡介
1、多線程的由來
一個進程(進程)在執行一個線程(線程中有很多函數或方法(後面簡稱Function))的時候,其中有一個Function執行的時候需要消耗一些時間,但是這個線程又必須同時執行這個Function之後的Function,問題來了,一個線程中的任何一個Function都必須等待其執行完成後才能執行後面的Function,如果要同時執行兩個或者多個Function,那麼,就必須多開一個或者多個線程,這就是多線程的產生。我想多線程最開始的誕生就是由這而來吧!
2、耗時操作的模擬試驗
2.1 迴圈測試
代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"bengin");
for (int i = 0; i < 10000000; i++) {
}
NSLog(@"end");
}
return 0;
}
控制台
2016-02-16 13:51:54.140 Test[1670:603696] bengin
2016-02-16 13:51:54.160 Test[1670:603696] end
Program ended with exit code: 0
結論一:迴圈一億次耗時0.02秒,電腦的運行速度是非常快的
2.2 操作棧區
代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"bengin");
for (int i = 0; i < 10000000; i++) {
int n = 1;
}
NSLog(@"end");
}
return 0;
}
控制台
2016-02-16 13:57:37.589 Test[1734:631377] bengin
2016-02-16 13:57:37.612 Test[1734:631377] end
Program ended with exit code: 0
結論二:對棧區操作一億次,耗時0.023秒
2.3 操作常量區
代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"bengin");
for (int i = 0; i < 10000000; i++) {
NSString *str = @"hellow";
}
NSLog(@"end");
}
return 0;
}
控制台
2016-02-16 14:03:59.003 Test[1763:659287] bengin
2016-02-16 14:03:59.113 Test[1763:659287] end
Program ended with exit code: 0
結論三:對常量區操作一億次,耗時0.11秒
2.4 操作堆區
代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"bengin");
for (int i = 0; i < 10000000; i++) {
NSString *str = [NSString stringWithFormat:@"%d",i];
}
NSLog(@"end");
}
return 0;
}
控制台
2016-02-16 14:09:03.673 Test[1786:673719] bengin
2016-02-16 14:09:10.705 Test[1786:673719] end
Program ended with exit code: 0
結論四:對堆區操作一億次耗時7秒多一些,較慢!
2.5 I/O操作
代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"bengin");
for (int i = 0; i < 10000000; i++) {
NSLog(@"%d",i);
}
NSLog(@"end");
}
return 0;
}
控制台輸出!正在跑中,一億次!!!先看截圖
CPU
再看記憶體
好吧,還在跑,現在已經達到10分鐘了,怕心疼本本炸掉!stop。。。
結論五:I/O操作非常慢,一億次10分鐘也沒能跑完!
最終結論:通過以上結論一、二、三、四、五得出一個結論,各個區的執行效率:棧區>常量區>堆區>I/O操作。同時也說明瞭一個問題,執行不同的方法會產什麼耗時操作。這是,為瞭解決耗時操作問題,多線程閃亮誕生!
3、進程和線程
先說說進程和線程吧!
3.1 進程
3.1.1 進程的概念:系統中正在運行的應用程式。
3.1.2 進程的特點:每個進程都運行在其專用且受保護的記憶體空間,不同的進程之間相互獨立,互不幹擾。
3.2 線程
3.2.1 線程的概念:線程是進程的執行任務的基本單元,一個進程的所有任務都是線上程中執行的。(每一個進程至少要有一條線程)。
3.2.2 線程的特點:線程在執行任務的時候是按順序執行的。如果要讓一條線程執行多個任務,那麼只能一個一個地並且按順序執行這些任務。也就是說,在同一時間,一條線程只能執行一個任務。
我們可以通過Mac中的活動監視器查看進程和線程,下圖!
4、多線程的概念及原理
4.1 多線程概念:1個進程可以開啟多條線程,多條線程可以同時執行不同的任務。
4.2 多線程原理:
前提是在單核CPU的情況下,同一時間,CPU只能處理一條線程,也就是說只有一條線程在執行任務。多線程同時執行,那是不可能的!但是是CPU快速地在多條線程之間進行調度和切換執行任務。如果CPU調度線程的速度足夠快,就會造成多條線程同時執行任務的”假象”,這種假象,就被美譽為:多線程!
5、多線程的優缺點和一個Tip
5.1 多線程的優點
可以適當提高程式的執行效率
也可以適當提高資源的利用率(CPU、記憶體利用率)
5.2 多線程的缺點
開啟一條線程需要占用一定的記憶體空間(預設情況下,每一條線程都占用512KB),如果開啟大量的線程,會占用大量的記憶體空間,從而降低程式的性能。
線程越多,CPU在調度和切換線程上的開銷就會越大。
線程數越多,程式的設計會越複雜。
5.3 Tip
開啟新的線程就會消耗資源,但是卻可以提高用戶體驗。在保證良好的用戶體驗的前提下,可以適當地開線程,一般開3-6條。
開啟一條新的線程,預設情況下,一條線程都是占用512KB,但是官方的文檔裡面給出的說明卻不是,為了得出真相,下麵做個小小的測試!
代碼int main(int argc, const char * argv[]) {
@autoreleasepool {
/** 操作主線程 /
NSLog(@"主線程預設 %tu", [NSThread currentThread].stackSize / 1024);
// 設置主線程的stackSize
[NSThread currentThread].stackSize = 1024 1024;
NSLog(@"主線程修改 %tu", [NSThread currentThread].stackSize / 1024);/** 操作子線程 */ NSThread *thread = [[NSThread alloc] init]; NSLog(@"thread預設 %tu", thread.stackSize / 1024); // 設置子線程的stackSize thread.stackSize = 8 * 1024; NSLog(@"thread修改 %tu", thread.stackSize / 1024); [thread start];
}
return 0;
}
控制台2016-02-17 08:36:02.652 Test[609:110129] 主線程預設 512
2016-02-17 08:36:02.654 Test[609:110129] 主線程修改 1024
2016-02-17 08:36:02.654 Test[609:110129] thread預設 512
2016-02-17 08:36:02.654 Test[609:110129] thread修改 8
結論七:證明瞭,不管什麼線程,預設都是512,最小為8.可能是官方文檔沒有及時更新吧!
6、主線程
6.1 主線程的概念:
一個應用程式在啟動運行後,系統會自動開啟1條線程,這條稱為”主線程”。
6.2 主線程的作用:主線程的作用主要用於處理UI界面刷新和UI時間!
6.3 結論:主線程上不能執行耗時操作,這樣會造成界面卡頓,給用戶一種不好的體驗。
7、技術方案
***
二、Pthread
1、函數
pthread_create(pthread_t *restrict, const pthread_attr_t *restrict, void *(*)(void *), void *restrict)
2、參數和返回值
- pthread_t *restrict 線程編號的地址
- const pthread_attr_t *restrict 線程的屬性
- void *(*)(void *) 線程要執行的函數void * (*) (void *)
- int * 指向int類型的指針 void * 指向任何類型的指針 有點類似OC中的id
void *restrict 要執行的函數的參數
返回值 int類型 0是成功 非0 是失敗
3、使用
代碼
#import <Foundation/Foundation.h>
#import <pthread/pthread.h>
void *demo(void *param) {
NSString *name = (__bridge NSString *)(param);
NSLog(@"hello %@ %@",name,[NSThread currentThread]);
return NULL;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//創建子線程
pthread_t pthread; //線程編號
NSString *test = @"test";
int result = pthread_create(&pthread, NULL, demo, (__bridge void *)(test));
NSLog(@"Began %@",[NSThread currentThread]);
if (result == 0) {
NSLog(@"成功");
}else {
NSLog(@"失敗");
}
}
return 0;
}
控制台
2016-02-16 22:00:57.401 Test[888:42585] Began <NSThread: 0x100502d70>{number = 1, name = main}
2016-02-16 22:00:57.403 Test[888:42615] hello test <NSThread: 0x100102a30>{number = 2, name = (null)}
2016-02-16 22:00:57.403 Test[888:42585] 成功
__bridge 橋接,把OC中的對象,傳遞給c語言的函數,使用__bridge
***三、NSThread
1、創建一個新的線程
方式一
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
方式二
[NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
方式三
[self performSelectorInBackground:@selector(demo) withObject:nil];
2、線程的狀態
線程狀態分為五種
- 創建 New
- 就緒 Runnable
運行 Running
- (void)start;
阻塞(暫停) Blocked
+ (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti;
死亡 Dead
+ (void)exit;
3、線程的屬性
線程有兩個重要的屬性:名稱和優先順序
3.1 名稱 name
設置線程名用於記錄線程,在出現異常時可以DeBug
3.2 優先順序,也叫做“服務質量”。threadPriority,取值0到1.
優先順序或者服務質量高的,可以優先調用,只是說會優先調用,但是不是百分之百的優先調用,這裡存在一個概率問題,內核里的演算法調度線程的時候,只是把優先順序作為一個考慮因素,還有很多個因數會決定哪個線程優先調用。這點得註意註意!!!
下麵是測試代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//新建狀態
NSThread *test1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
test1.name = @"test1";
//線程的優先順序
test1.threadPriority = 1.0;
//就緒狀態
[test1 start];
//新建狀態
NSThread *test2= [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
test2.name = @"test2";
test2.threadPriority = 0;
//就緒狀態
[test2 start];
}
//線程執行完成之後會自動銷毀
- (void)demo {
for (int i = 0; i < 20; i++) {
NSLog(@"%d--%@",i,[NSThread currentThread]);
}
}
控制台
2016-02-16 22:43:28.182 05-線程狀態[1241:78688] 0--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.182 05-線程狀態[1241:78689] 0--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.182 05-線程狀態[1241:78688] 1--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.182 05-線程狀態[1241:78688] 2--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.182 05-線程狀態[1241:78689] 1--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 3--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78689] 2--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 4--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 5--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78689] 3--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 6--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 7--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.183 05-線程狀態[1241:78689] 4--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.183 05-線程狀態[1241:78688] 8--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 9--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 10--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78689] 5--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 11--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78689] 6--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 12--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 13--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78689] 7--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 14--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78688] 15--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.185 05-線程狀態[1241:78688] 16--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.184 05-線程狀態[1241:78689] 8--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.185 05-線程狀態[1241:78688] 17--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.185 05-線程狀態[1241:78688] 18--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.185 05-線程狀態[1241:78689] 9--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.185 05-線程狀態[1241:78688] 19--<NSThread: 0x7fead2017f30>{number = 2, name = test1}
2016-02-16 22:43:28.185 05-線程狀態[1241:78689] 10--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.186 05-線程狀態[1241:78689] 11--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.186 05-線程狀態[1241:78689] 12--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.186 05-線程狀態[1241:78689] 13--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.186 05-線程狀態[1241:78689] 14--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 15--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 16--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 17--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 18--<NSThread: 0x7fead050a250>{number = 3, name = test2}
2016-02-16 22:43:28.187 05-線程狀態[1241:78689] 19--<NSThread: 0x7fead050a250>{number = 3, name = test2}
結論六:優先順序高,不一定先執行,只能說明先執行的概率要大一些!!!
四、互斥鎖
1、訪問共用資源引入問題!
1.1 問題?
不同的線程要訪問共用的資源,而且對共用的資源做操作,由於上面結論六
得出服務質量和優先順序不能決定線程執行的先後順序,那麼問題來了,一個線程對共用資源做了修改,而另外一個線程拿到的是未被修改之前資源,這是這個線程也對該資源做了修改,現在請問,兩個線程都對該資源做了不同的修改,那麼這個修改應該算誰的?!?!這就是問題所在!!!
1.2 問題分析
1.3 問題解決
這個文檔里盜的圖!
解決方案很簡單,就是用一把鎖鎖住共用資源,等待線程1對其操作完畢後再打開,讓線程2來執行,這就是傳說中的互斥鎖
!!!
2、互斥鎖介紹
2.1 互斥鎖代碼
@synchronized(鎖對象) { 需要鎖定的代碼 }
2.2 互斥鎖的作用
可以防止因多線程執行順序不定導致的搶奪資源造成的數據安全的問題
2.3 真相:互斥鎖其實就是同步的意思,也就是按順序執行!
3、互斥鎖原理
每個NSObject對象內部都有一把鎖,當線程要進入synchronized到對象的時候就要判斷,鎖是否被打開,如果打開,進入執行,如果鎖住,繼續等待,這就是互斥鎖的原理!
4、互斥鎖和自旋鎖
自旋鎖就是atomic!
4.1 原子屬性和非原子屬性(nonatomic 和 atomic)
- nonatomic:非原子屬性,不會為 setter 方法加鎖。
- atomic: 原子屬性,為 setter 方法加鎖(預設就是atomic)。
- 通過給 setter 加鎖,可以保證同一時間只有一個線程能夠執行寫入操作(setter),但是同一時間允許多個線程執行讀取操作(getter)。atomic本身就有一把自旋鎖。
這個特點叫做”單寫多讀”: 單個線程寫入,多個線程讀取。 - atomic 只能保證寫入數據的時候是安全的,但不能保證同時讀寫的時候是安全的。所以,不常使用!
- 通過給 setter 加鎖,可以保證同一時間只有一個線程能夠執行寫入操作(setter),但是同一時間允許多個線程執行讀取操作(getter)。atomic本身就有一把自旋鎖。
4.2 nonatomic 和 atomic 的對比
atomic:線程安全(執行setter方法的時候),需要消耗大量的資源。
nonatomic:非線程安全,適合記憶體小的移動設備。
4.3 互斥鎖和自旋鎖的區別
互斥鎖
如果發現其它線程正在執行鎖定代碼,線程會進入休眠(阻塞狀態),等其它線程時間片到了打開鎖後,線程就會被喚醒(執行)。
自旋鎖
如果發現有其它線程正在執行鎖定代碼,線程會以死迴圈的方式,一直等待鎖定的代碼執行完成。
***
五、GCD
1、GCD介紹
全稱Grand Central Dispatch,可翻譯為”牛逼的中樞調度器”
純C語言開發,是蘋果公司為多核的並行運算提出的解決方案,會自動利用更多的CPU內核(比如雙核、四核),可以自動管理線程的生命周期(創建線程、調度任務、銷毀線程)。
2、GCD的兩個核心
2.1 任務
- 執行的操作,在GCD中,任務是通過 block來封裝的。並且任務的block沒有參數也沒有返回值。
2.2 隊列
- 存放任務
包括
- 串列隊列
- 併發隊列
- 主隊列
- 全局隊列
隊列的類型
3、函數
3.1 GCD函數
3.1.1 同步 dispatch_sync
同步:任務會在當前線程執行,因為同步函數不具備開新線程的能力。
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
3.1.2 非同步 dispatch_async
非同步:任務會在子線程執行,因為非同步函數具備開新線程的能力。
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
3.2 GCD使用步驟:
- 創建隊列,或則獲取隊列
- 創建任務
- 將任務添加到隊列中
- GCD會自動將隊列中的任務取出,放到對應的線程中執行
- 任務取出遵循隊列的FIFO原則:先進先出,後進後出
示例代碼
// 1. 獲取全局隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 創建任務
dispatch_block_t task = ^ {
NSLog(@"hello %@", [NSThread currentThread]);
};
// 3. 將任務添加到隊列,並且指定執行任務的函數
dispatch_async(queue, task);
通常寫成一句代碼
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"hello %@", [NSThread currentThread]);
});
4、串列隊列和併發隊列
4.1 串列隊列 (Serial Dispatch Queue)
4.1.1 特點
先進先出,按照順序執行,並且一次只能調用一個任務
無論隊列中所指定的執行任務的函數是同步還是非同步,都必須等待前一個任務執行完畢才可以調用後面的人
4.1.2 創建一個串列隊列
方法一
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
方法二
dispatch_queue_t queue = dispatch_queue_create("test", NULL);
4.1.3 串列隊列,同步執行
代碼:
// 1、創建串列隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 2、將任務添加到隊列,並且指定同步執行
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%@--%d",[NSThread currentThread],i);
});
}
列印結果:
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--0
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--1
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--2
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--3
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--4
2016-02-25 16:31:07.849 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--5
2016-02-25 16:31:07.850 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--6
2016-02-25 16:31:07.850 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--7
2016-02-25 16:31:07.850 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--8
2016-02-25 16:31:07.850 test[1924:376468] <NSThread: 0x7ff040404370>{number = 1, name = main}--9
結論:串列隊列,同步執行,不開新線程,按順序執行
4.1.4 串列隊列,非同步執行
代碼:
// 1、創建串列隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 2、將任務添加到隊列,並且指定同步執行
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@--%d",[NSThread currentThread],i);
});
}
列印結果:
2016-02-25 17:08:32.167 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--0
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--1
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--2
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--3
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--4
2016-02-25 17:08:32.168 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--5
2016-02-25 17:08:32.169 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--6
2016-02-25 17:08:32.169 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--7
2016-02-25 17:08:32.169 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--8
2016-02-25 17:08:32.169 test[1959:391722] <NSThread: 0x7fbb98d24fa0>{number = 2, name = (null)}--9
結論:串列隊列,非同步執行,開啟一條新的線程,按順序執行
4.2 併發隊列 (Concurrent Dispatch Queue)
4.2.1 特點
- 併發同時調度隊列中的任務去執行
- 如果當前調度的任務是同步執行的,會等待當前任務執行完畢後,再調度後續的任務
- 如果當前調度的任務是非同步執行的,同時底層線程池有可用的線程資源,就不會等待當前任務,直接調度任務到新線程去執行。
4.2.2 創建併發隊列
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
4.2.3 併發隊列,同步執行
代碼:
// 1. 創建併發隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 2. 將任務添加到隊列, 並且指定同步執行
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
輸出:
2016-02-25 17:18:38.039 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 0
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 1
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 2
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 3
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 4
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 5
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 6
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 7
2016-02-25 17:18:38.040 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 8
2016-02-25 17:18:38.041 test[1979:399667] <NSThread: 0x7ffef86024b0>{number = 1, name = main} 9
結論:併發隊列,同步執行,不開線程,順序執行
4.2.4 併發隊列,非同步執行
代碼:
// 1. 創建併發隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 2. 將任務添加到隊列, 並且指定同步執行
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
輸出:
2016-02-25 17:22:59.357 test[1992:403694] <NSThread: 0x7fe531c1a9b0>{number = 7, name = (null)} 6
2016-02-25 17:22:59.356 test[1992:403684] <NSThread: 0x7fe531d18fa0>{number = 3, name = (null)} 1
2016-02-25 17:22:59.356 test[1992:403689] <NSThread: 0x7fe534300610>{number = 5, name = (null)} 3
2016-02-25 17:22:59.356 test[1992:403683] <NSThread: 0x7fe531e94d80>{number = 2, name = (null)} 0
2016-02-25 17:22:59.356 test[1992:403692] <NSThread: 0x7fe531e9df80>{number = 6, name = (null)} 4
2016-02-25 17:22:59.356 test[1992:403693] <NSThread: 0x7fe531d18f40>{number = 8, name = (null)} 5
2016-02-25 17:22:59.356 test[1992:403695] <NSThread: 0x7fe5343015e0>{number = 9, name = (null)} 7
2016-02-25 17:22:59.357 test[1992:403688] <NSThread: 0x7fe531c16e30>{number = 4, name = (null)} 2
2016-02-25 17:22:59.357 test[1992:403694] <NSThread: 0x7fe531c1a9b0>{number = 7, name = (null)} 9
2016-02-25 17:22:59.357 test[1992:403696] <NSThread: 0x7fe531c237a0>{number = 10, name = (null)} 8
結論:開啟足夠多的線程,不按照順序執行
CPU在調度的時候以最高效的方式調度和執行任務,所以會開啟多條線程,因為併發,執行順序不一定
5、主隊列
5.1 主隊列
主隊列是系統提供的,無需自己創建,可以通過dispatch_get_main_queue()函數來獲取。
5.2 特點
- 添加到主隊列的任務只能由主線程來執行。
先進先出的,只有當主線程的代碼執行完畢後,主隊列才會調度任務到主線程執行
5.3 主隊列 非同步執行
代碼
// 1. 獲取主隊列 dispatch_queue_t q = dispatch_get_main_queue(); // 2. 將任務添加到主隊列, 並且指定非同步執行 for (int i = 0; i < 10; i++) { dispatch_async(q, ^{ NSLog(@"%@ %d", [NSThread currentThread], i); }); } // 先執行完這句代碼, 才會執行主隊列中的任務 NSLog(@"hello %@", [NSThread currentThread]);
列印
2016-02-25 21:10:43.293 test[773:786816] hello <NSThread: 0x7ff158c05940>{number = 1, name = main}
2016-02-25 21:10:43.295 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 0
2016-02-25 21:10:43.295 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 1
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 2
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 3
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 4
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 5
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 6
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 7
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 8
2016-02-25 21:10:43.296 test[773:786816] <NSThread: 0x7ff158c05940>{number = 1, name = main} 9
列印結果得出的一些結論
- 在主線程順序執行,不開啟新的線程
- 主隊列的特點:只有當主線程空閑時,主隊列才會調度任務到主線程執行
- 主隊列就算是非同步執行也不會開啟新的線程
- 主隊列相當於一個全局的串列隊列
- 主隊列和串列隊列的區別
- 串列隊列:必須等待一個任務執行完畢,才會調度下一個任務。
- 主隊列:如果主線程上有代碼執行,主隊列就不調度任務。
5.4 主隊列 同步執行(死鎖)
代碼
NSLog(@"begin");
// 1. 獲取主隊列
dispatch_queue_t q = dispatch_get_main_queue();
// 2. 將任務添加到主隊列, 並且指定同步執行
// 死鎖
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"end");
列印
2016-02-25 21:19:25.986 test[791:790967] begin
列印結果可以看出,不是想要的結果,這時候發生了死鎖
在主線程執行,主隊列同步執行任務,會發生死鎖,主線程和主隊列同步任務相互等待,造成死鎖
解決辦法
代碼
NSLog(@"begin");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"--- %@", [NSThread currentThread]);
// 1. 獲取主隊列
dispatch_queue_t q = dispatch_get_main_queue();
// 2. 將任務添加到主隊列, 並且指定同步執行
// 死鎖
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
});
NSLog(@"end");
列印
2016-02-25 21:23:23.205 test[803:794826] begin
2016-02-25 21:23:23.206 test[803:794826] end
2016-02-25 21:23:23.206 test[803:794866] --- <NSThread: 0x7f8830514cb0>{number = 2, name = (null)}
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 0
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 1
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 2
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 3
2016-02-25 21:23:23.209 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 4
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 5
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 6
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 7
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 8
2016-02-25 21:23:23.210 test[803:794826] <NSThread: 0x7f8830507dd0>{number = 1, name = main} 9
列印結果可以看出,當我們將主隊列同步執行任務放到子線程去執行,就不會出現死鎖。由於將主隊列同步放到了子線程中執行,主隊列同步任務無法阻塞主線程執行代碼,因此主線程可以將主線程上的代碼執行完畢。當主線程執行完畢之後,就會執行主隊列裡面的任務。
6、全局隊列
全局隊列是系統提供的,無需自己創建,可以直接通過dispatch_get_global_queue(long identifier, unsigned long flags);函數來獲取。
6.1 全局隊列 非同步執行
代碼
// 1. 獲取全局隊列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 將任務添加到全局隊列, 非同步執行
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
列印輸出
2016-02-25 21:29:06.978 test[816:799523] 1 <NSThread: 0x7fd428e15760>{number = 3, name = (null)}
2016-02-25 21:29:06.978 test[816:799530] 4 <NSThread: 0x7fd428d2fbb0>{number = 6, name = (null)}
2016-02-25 21:29:06.978 test[816:799522] 0 <NSThread: 0x7fd428f094e0>{number = 2, name = (null)}
2016-02-25 21:29:06.978 test[816:799529] 3 <NSThread: 0x7fd428c0e1b0>{number = 5, name = (null)}
2016-02-25 21:29:06.978 test[816:799532] 6 <NSThread: 0x7fd428f06740>{number = 7, name = (null)}
2016-02-25 21:29:06.978 test[816:799533] 7 <NSThread: 0x7fd428d37be0>{number = 8, name = (null)}
2016-02-25 21:29:06.978 test[816:799531] 5 <NSThread: 0x7fd428e0c490>{number = 9, name = (null)}
2016-02-25 21:29:06.978 test[816:799526] 2 <NSThread: 0x7fd428d3e4b0>{number = 4, name = (null)}
2016-02-25 21:29:06.979 test[816:799534] 8 <NSThread: 0x7fd428d36ab0>{number = 10, name = (null)}
2016-02-25 21:29:06.979 test[816:799523] 9 <NSThread: 0x7fd428e15760>{number = 3, name = (null)}
特點:
1、全局隊列的工作特性跟併發隊列一致。 實際上,全局隊列就是系統為了方便程式員,專門提供的一種特殊的併發隊列。
2、全局隊列和併發隊列的區別:
* 全局隊列沒有名稱,無論ARC還是MRC都不需要考慮記憶體釋放,日常開發,建議使用全局隊列
* 併發隊列有名稱,如果在MRC開發中,需要使用dispatch_release來釋放相應的對象,dispatch_barrier 必須使用自定義的併發隊列,開發第三方框架,建議使用併發隊列
3、函數
dispatch_get_global_queue(long identifier, unsigned long flags);
這個函數中有兩個參數:
第一個參數: identifier
iOS7.0,表示的是優先順序:
DISPATCH_QUEUE_PRIORITY_HIGH = 2; 高優先順序
DISPATCH_QUEUE_PRIORITY_DEFAULT = 0; 預設優先順序
DISPATCH_QUEUE_PRIORITY_LOW = -2; 低優先順序
DISPATCH_QUEUE_PRIORITY_BACKGROUND = INT16_MIN; 後臺優先順序
iOS8.0開始,推薦使用服務質量(QOS):
QOS_CLASS_USER_INTERACTIVE = 0x21; 用戶交互
QOS_CLASS_USER_INITIATED = 0x19; 用戶期望
QOS_CLASS_DEFAULT = 0x15; 預設
QOS_CLASS_UTILITY = 0x11; 實用工具
QOS_CLASS_BACKGROUND = 0x09; 後臺
QOS_CLASS_UNSPECIFIED = 0x00; 未指定
通過對比可知: 第一個參數傳入0,可以同時適配iOS7及iOS7以後的版本。
服務質量和優先順序是一一對應的:
DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
第二個參數: flags
為未來保留使用的,始終傳入0。
Reserved for future use.
7、GCD總結
1、開不開線程,由執行任務的函數決定
- 同步執行不開線程
- 非同步執行開線程
2、非同步執行任務,開幾條線程由隊列決定
- 串列隊列,只會開一條線程,因為一條就足夠了
- 併發隊列,可以開多條線程,具體開幾條由線程池決定
對主隊列而言,不管是同步執行還是非同步執行,都不會開線程。
最後盜圖總結一張
六、NSOperation
1、NSOperation簡介
1.1 NSOperation與GCD的區別:
- OC語言中基於 GCD 的面向對象的封裝;
- 使用起來比 GCD 更加簡單;
- 提供了一些用 GCD 不好實現的功能;
- 蘋果推薦使用,使用 NSOperation 程式員不用關心線程的生命周期
1.2NSOperation的特點
- NSOperation 是一個抽象類,抽象類不能直接使用,必須使用它的子類
- 抽象類的用處是定義子類共有的屬性和方法
2、核心概念
將操作添加到隊列,非同步執行。相對於GCD創建任務,將任務添加到隊列。
將NSOperation添加到NSOperationQueue就可以實現多線程編程
3、操作步驟
- 先將需要執行的操作封裝到一個NSOperation對象中
- 然後將NSOperation對象添加到NSOperationQueue中
- 系統會自動將NSOperationQueue中的NSOperation取出來
- 將取出的NSOperation封裝的操作放到一條新線程中執行
4、NSInvocationOperation
No1.
代碼
- (void)viewDidLoad {
[super viewDidLoad];
//創建操作,然後調用操作的start方法
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
NSLog(@"%d",op.isFinished);
[op start];
NSLog(@"%d",op.isFinished);
}
- (void)demo {
NSLog(@"hello %@",[NSThread currentThread]);
}
列印輸出
2016-02-25 22:12:30.054 test[892:834660] 0
2016-02-25 22:12:30.054 test[892:834660] hello <NSThread: 0x7fad12704f80>{number = 1, name = main}
2016-02-25 22:12:30.054 test[892:834660] 1
結論:[op start]在主線程中調用的,所以執行的線程也會是在主線程執行! 重覆調用start也只會執行一次,因為NSOperation會有一個屬性去記住,是否已經完成了該操作!
No2.
代碼
- (void)viewDidLoad {
[super viewDidLoad];
// 創建操作,將操作添加到NSOperationQueue中,然後就會非同步的自動執行
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
//隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//把操作添加到隊列
[queue addOperation:op];
}
- (void)demo {
NSLog(@"hello %@",[NSThread currentThread]);
}
列印
2016-02-25 22:21:44.999 test[912:842412] hello <NSThread: 0x7fab92610080>{number = 2, name = (null)}
將操作添加到NSOperationQueue中,然後就會非同步的自動執行
5、NSBlockOperation
NSBlockOperation 中使用block的方式讓所有代碼邏輯在一起,使用起來更加簡便。
NO1.
代碼
//創建操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"hello %@",[NSThread currentThread]);
}];
//更新op的狀態,執行main方法,不會開新線程
[op start];
輸出
2016-02-25 22:25:30.442 test[923:846208] hello <NSThread: 0x7fd410d055a0>{number = 1, name = main}
NO2.
代碼
// 創建隊列,創建操作,將操作添加到隊列中執行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"hello %@",[NSThread currentThread]);
}];
[queue addOperation:op];
輸出
2016-02-25 22:26:48.064 test[934:848038] hello <NSThread: 0x7fc6bbb24c80>{number = 2, name = (null)}
NO3.
代碼
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"hello %@",[NSThread currentThread]);
}];
輸出
2016-02-25 22:27:56.445 test[945:850128] hello <NSThread: 0x7f98dbc2cae0>{number = 2, name = (null)}
創建隊列,添加block形式的操作
七、案例
線程之間的通信問題
技術方案:NSOperation
[self.queue addOperationWithBlock:^{
NSLog(@"非同步下載圖片");
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"回到主線程,更新UI");
}];
}];
技術方案:GCD
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"下載圖片---%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新圖片的顯示 -%@",[NSThread currentThread]);
});
});
這文章寫了好久,,過年一直到現在,終於寫完。。。
**轉載請註明來自吃飯睡覺擼碼的博客 http://www.cnblogs.com/Erma-king/,並包含相關鏈接。**