不知道大家面試iOS軟件工程師的時候有沒有遇到問多線程的?反正我遇到的還是蠻多的。下面是我面試時候的一個小場景!有點不堪??,看完不許笑啊.....
面試官:你平常在開發中有用到多線程嗎?
我:有!
面試官:那你說說你在開發的時候都有哪些場景用到多線程啊?
我:很多場景...(氣氛瞬間有點不對勁了??)
面試官:那你說說多線程都有哪些框架?
我:NSThread
、NSOperationQueue
、GCD
(很洋氣的用英語講出來??)
面試官:說說你理解的三者的優缺點(氣氛稍稍有點緩解)
我:(網上看到過就背下來了,心里有點小得意,滔滔不絕的說完如下話語)
1.NSThread:
使用NSThread對象建立一個線程非常方便;
但是要使用NSThread管理多個線程非常困難,不推薦使用;
技巧!使用[NSThread currentThread]跟蹤任務所在線程,適用于這三種技術.
2.NSOperation/NSOperationQueue:
是使用GCD實現的一套Objective-C的API;
是面向對象的多線程技術;
提供了一些在GCD中不容易實現的特性,如:限制最大并發數量,操作之間的依賴關系.
3.GCD---Grand Central Dispatch:
是基于C語言的底層API;
用Block定義任務,使用起來非常靈活便捷;
提供了更多的控制能力以及操作隊列中所不能使用的底層函數.
面試官:面無表情的說,回去等通知.....(結果..........大家懂的??)
這就是Bison剛出道時眼中的多線程??
這種局面,究根結底,是自己對底層的東西不夠透徹只停留在怎么去使用它,而不知道底層是怎么實現的。下面讓我很嚴肅的和大家說說多線程到底是一個什么樣子的!
對于單線程的應用而言,整個應用只是一個順序執行流,當執行到某個耗時操作時,主線程就會被阻塞,應用就卡在那無法繼續執行,因此單線程的應用體驗度很低,總感覺像手機卡似得,就像一條小河北阻塞了,只有打通了才能繼續有水流到下一個地方放一樣。而多線程則更像一條河有無數的分支,這條阻塞了還有其他的分支在運行,影響不到大局。希望我的比喻夠恰當啊.......??
iOS開發平臺提供了非常優秀的多線程支持,程序可以通過很簡單的方式來開啟多線程,提供了我上述場景所說的多線程編程。總之iOS已經降低了開發多線程應用的繁瑣,讓開發者能輕松、簡單的開發多線程應用。
幾乎所有的操作系統都支持同時運行多個任務,一個任務通常就是一個程序,每個運行的程序就是一個進程。當一個程序運行時,內部可能包含了多個順序執行流,每個執行流就是一個線程。在此就不得不說下進程和線程有什么區別了,很早以前我還是會混淆的哦??。下面是我的理解....
僅供參考
當一個程序進入內存運行后,即變成了一個進程。進程是處于運行過程中的程序,并且具有一定的獨立功能,是系統進行資源分配和調度的一個獨立單位。
線程是進程的組成部分,一個進程可以擁有多個線程,而一個線程必須擁有一個父進程,線程可以擁有自己的堆棧、自己的程序計數器和自己的局部變量,但不再擁有系統資源,與父進程中的其他線程共享該進程所擁有的全部資源。也因此多線程編程更加的方便;值得注意的是確保每個線程不會妨礙該進程的其他線程。
通過上面的啰嗦,我想大家對多線程都有一定的了解了。在此總結一下多線程編程的幾個優點,如有遺漏,歡迎留言補充:
- 進程間不能共享內存,但線程之間共享內存非常容易。(這是錯誤的,共享內存是一種最為高效的進程間通信方式,進程可以直接讀寫內存,而不需要任何數據的拷貝,多謝網友@皮皮彭指正)
- 系統創建進程需要為該進程重新分配系統資源,但創建線程則代價小的多,因此使用多線程來實現多任務并發比多進程的效率高。
- iOS提供了多種多線程實現方式,從而簡化了iOS的多線程編程。
接下來Bison將分別講解iOS開發多線程中的用法
NSThread
iOS使用NSThread
類代表線程,創建新線程也就是創建NSThread
對象。
創建NSThread
有倆種方式。
//創建一個新線程對象
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
//創建并啟動新線程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
上面倆種方式的本質都是將target
對象的selector
方法轉換為線程執行體,其中selector
方法最多可以接收一個參數,而argument
就代表傳給selector
方法的參數。
這倆種創建新線程的方式并沒有明顯的區別,只是第一種方式是一個實例化方法,該方法返回一個NSThread
對象,必須調用 start
方法啟動線程:另一種不會返回NSThread
對象,因此這種方法會直接創建并啟動新線程。
下面我們隨便舉個栗子,代碼如下
- (void)viewDidLoad
{
[super viewDidLoad];
for(int i = 0 ; i < 100 ; i++)
{
NSLog(@"===%@===%d" , [NSThread currentThread].name , i);
if(i == 7)
{
// 創建線程對象
NSThread *thread = [[NSThread alloc]initWithTarget:self
selector:@selector(run) object:nil];
// 啟動新線程
[thread start];
// 創建并啟動新線程
// [NSThread detachNewThreadSelector:@selector(run) toTarget:self
// withObject:nil];
}
}
}
- (void)run
{
for(int i = 0 ; i < 100 ; i++)
{
NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
}
}
運行一下,打印日志如下:
//主線程正在運行
2016-01-12 14:30:46.400 NSThreadTest[7498:104912] ======0
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======1
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======2
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======3
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======4
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======5
2016-01-12 14:30:46.401 NSThreadTest[7498:104912] ======6
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======7
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======8
//新線程開始運行
2016-01-12 14:30:46.402 NSThreadTest[7498:104956] ---------0
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======9
2016-01-12 14:30:46.402 NSThreadTest[7498:104956] ---------1
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======10
2016-01-12 14:30:46.402 NSThreadTest[7498:104956] ---------2
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======11
2016-01-12 14:30:46.402 NSThreadTest[7498:104912] ======12
2016-01-12 14:30:46.402 NSThreadTest[7498:104956] ---------3
................
由上不難看出,這是倆個線程并發執行的效果。除此之外上面的??還用到了
+ (NSThread *)currentThread;
方法,該方法總是返回當前正在執行的線程對象。
當線程被創建并啟動以后,它既不是一啟動就進入了執行狀態,也不是一直處于執行狀態。即使線程開始運行以后,它也不可能一直“霸占”著CPU獨自運行,所以CPU需要再多個線程之間切換,于是線程狀態也會多次在運行、就緒狀態之間的切換。
當程序創建了一個線程之后,該線程就處于新建狀態,此時它和其他Objective-C對象一樣,僅僅由系統為其分配了內存,并初始化了其他成員變量的值。此時的線程對象沒有表現出任何線程的動態特征,程序也不會執行線程的線程執行體。
當線程對象調用了start
方法之后,該線程處于就緒狀態,系統會為其創建方法調用棧和程序計數器,處于這種狀態中的線程并沒有開始運行,它只是表示該線程可以運行了。至于該線程何時運行,取決于系統的調度。
終止子線程
線程會以如下3中方式之一結束,結束后就處于死亡狀態。
線程執行體方法執行完成,線程正常結束。
線程執行過程中出現了錯誤。
直接調用
NSThread
類的exit方法來終止當前正在執行的線程。
為了測試木個線程是否正在運行,可以調用線程對象的isExecuting
、isFinished
方法,當線程正處于執行過程中時,調用isExecuting
方法會返回YES,當線程執行完后,調用isFinished
方法也返回YES。
如果希望在UI線程中終止子線程,NSThread
并沒有提供方法來終止某個子線程,雖然提供了cancel
方法,但該方法僅僅只是改變該線程的狀態,導致該線程的isCancelled方法返回NO,而不是真正終止該線程。
為了在UI線程中終止子線程,可以向子線程發送一個信號,然后在子線程的線程執行體方法中進行判斷,如果子線程收到過終止信號,程序應該調用exit
方法來終止當前正在執行的循環。下面舉個??,代碼如下:
NSThread* thread;
- (void)viewDidLoad
{
[super viewDidLoad];
// 創建新線程對象
thread = [[NSThread alloc] initWithTarget:self selector:@selector(run)
object:nil];
// 啟動新線程
[thread start];
}
- (void)run
{
for(int i = 0 ; i < 100 ; i++)
{
if([NSThread currentThread].isCancelled)
{
// 終止當前正在執行的線程
[NSThread exit];
}
NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
// 每執行一次,線程暫停0.5秒
[NSThread sleepForTimeInterval:0.5];
}
}
- (IBAction)cancelThread:(id)sender
{
// 取消thread線程,調用該方法后,thread的isCancelled方法將會返回NO
[thread cancel];
}
如果需要讓當前正在執行的線程暫停一段時間,并進入阻塞狀態,則可以通過調用如下方法。
// 讓當前正在執行的線程暫停到date代表的時間,并進入阻塞狀態。
+ (void)sleepUntilDate:(NSDate *)date;
// 讓當前正在執行的線程暫停到ti秒,并進入阻塞狀態。
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
每個線程執行時都具有一定的優先級,優先級高的線程獲得較多的執行機會,而優先級的則反之。每個子線程默認的優先級是0.5.
NSThread
通過如下代碼演示優先級。
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"UI線程的優先級為:%g" , [NSThread threadPriority]);
// 創建第一個線程對象
NSThread* thread1 = [[NSThread alloc]
initWithTarget:self selector:@selector(run) object:nil];
// 設置第一個線程對象的名字
thread1.name = @"線程A";
NSLog(@"線程A的優先級為:%g" , thread1.threadPriority);
// 設置使用最低優先級
thread1.threadPriority = 0.0;
// 創建第二個線程對象
NSThread* thread2 = [[NSThread alloc]
initWithTarget:self selector:@selector(run) object:nil];
// 設置第二個線程對象的名字
thread2.name = @"線程B";
NSLog(@"線程B的優先級為:%g" , thread2.threadPriority);
// 設置使用最高優先級
thread2.threadPriority = 1.0;
// 啟動2個線程
[thread1 start];
[thread2 start];
}
- (void)run
{
for(int i = 0 ; i < 100 ; i++)
{
NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
}
}
運行所得日志如下
2016-01-12 16:26:26.451 PriorityTest[10135:150983] UI線程的優先級為:0.5
//新線程默認優先級
2016-01-12 16:26:26.452 PriorityTest[10135:150983] 線程A的優先級為:0.5
2016-01-12 16:26:26.452 PriorityTest[10135:150983] 線程B的優先級為:0.5
//改變優先級后,優先級高的線程,獲取更多的執行機會
2016-01-12 16:26:26.453 PriorityTest[10135:151058] -----線程B----0
2016-01-12 16:26:26.453 PriorityTest[10135:151058] -----線程B----1
2016-01-12 16:26:26.453 PriorityTest[10135:151058] -----線程B----2
2016-01-12 16:26:26.454 PriorityTest[10135:151058] -----線程B----3
2016-01-12 16:26:26.454 PriorityTest[10135:151058] -----線程B----4
2016-01-12 16:26:26.454 PriorityTest[10135:151058] -----線程B----5
2016-01-12 16:26:26.454 PriorityTest[10135:151058] -----線程B----6
2016-01-12 16:26:26.465 PriorityTest[10135:151056] -----線程A----0
今天暫時寫到這吧....,有點長了??
好文推薦:詳解持久化Core Data框架的原理以及使用---轉自Bison的技術博客
原文地址:http://allluckly.cn
如對你有幫助,請不要吝惜你的star和喜歡哦!
推薦一款學習iOS開發的app_____|______| | 傳送門
技術交流群:534926022(免費) 511040024(0.8/人付費)
版權歸?Bison所有 如需轉載請保留原文超鏈接地址!否則后果自負!