Bison眼中的iOS開發多線程是這樣的(一)

allluckly.cn.jpg

不知道大家面試iOS軟件工程師的時候有沒有遇到問多線程的?反正我遇到的還是蠻多的。下面是我面試時候的一個小場景!有點不堪??,看完不許笑啊.....

面試官:你平常在開發中有用到多線程嗎?

我:有!

面試官:那你說說你在開發的時候都有哪些場景用到多線程啊?

我:很多場景...(氣氛瞬間有點不對勁了??)

面試官:那你說說多線程都有哪些框架?

我:NSThreadNSOperationQueueGCD(很洋氣的用英語講出來??)

面試官:說說你理解的三者的優缺點(氣氛稍稍有點緩解)

我:(網上看到過就背下來了,心里有點小得意,滔滔不絕的說完如下話語)

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方法來終止當前正在執行的線程。

為了測試木個線程是否正在運行,可以調用線程對象的isExecutingisFinished方法,當線程正處于執行過程中時,調用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

今天暫時寫到這吧....,有點長了??

Bison眼中的iOS開發多線程是這樣的(二)

博主app上線啦,快點此來圍觀吧

好文推薦:詳解持久化Core Data框架的原理以及使用---轉自Bison的技術博客

原文地址:http://allluckly.cn

如對你有幫助,請不要吝惜你的star和喜歡哦!

推薦一款學習iOS開發的app_____|______| | 傳送門

技術交流群:534926022(免費) 511040024(0.8/人付費)

版權歸?Bison所有 如需轉載請保留原文超鏈接地址!否則后果自負!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本文選譯自《Threading Programming Guide》。 導語 線程技術作為在單個應用程序中并發執行...
    巧巧的二表哥閱讀 2,475評論 4 24
  • 本文首發CSDN,如需轉載請與CSDN聯系。 記得第一次讀這個文檔還是3年前,那時也只是泛讀。如今關于iOS多線程...
    DevTalking閱讀 975評論 0 5
  • Object C中創建線程的方法是什么?如果在主線程中執行代碼,方法是什么?如果想延時執行代碼、方法又是什么? 1...
    AlanGe閱讀 1,794評論 0 17
  • 王可出事已經是十二月。那天早上下雨,金水和小燁、祝勇三個站在走廊上吃早餐,看到三四個一監區的服刑人員把王可叫了出去...
    甘醇閱讀 273評論 0 0
  • 找一個差不多的人 回到小城 開一家賺錢的小店 跟父母姐姐大家都住一起 不要孩子 每天吃飯 聽歌 看書 散步 看電視...
    Oshiruko閱讀 174評論 0 0