IOS多線程編程簡介

JefferyGott 8年前發布 | 9K 次閱讀 多線程 iOS開發 移動開發

IOS多線程編程簡介

基本概念

線程:線程是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務。線程是獨立調度和分派的基本單位。同一進程中的多條線程將共享該進程中的全部系統資源,但是自有調用堆棧和寄存器環境。

進程:進程是計算機中已運行程序的實體。其本身并不是幾部運行單位,是線程的容器。

任務:任務(task)用于指代抽象的概念,表示需要執行工作,具體可以是一個函數或者一個block。

IOS常用的多線程編程技術

IOS常用的多線程編程技術包括:NSThread、Cocoa NSOperation、GCD(grand central dispatch)。其優缺點對比如下表所示:

多線程技術 優點 缺點
NSThread 輕量級最低,相對簡單 需要手動管理所有的線程活動(生命周期,休眠,同步等)線程同步對數據的加鎖會有一定的系統開銷
Cocoa NSOperation 自帶線程周期管理,可只關注自己處理邏輯 NSOperation是面向對象的抽象類,實現只能是其子類(NSInvocationOperation和NSBlockOperation),對象需要添加到NSOperationQueue隊列里執行
GCD 效率高,可避免并發陷阱 基于C而非OC實現

如果對性能效率要求較高,處理大量并發時用GCD,簡單而安全的可用NSOPeration或者NSThread。Apple推薦使用GCD

NSThread

NSThread具體的底層實現機制是Mach線程,實現技術有三種Cocoa threads、POSIX threads(UNIX)、Multiprocessing Services。

使用方式

  1. 顯式創建方式:
    -(id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 
    +(void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
  2. 隱式創建方式:
    [Object performSelectorInBackground:@selector(doSomething)withObject:nil];

    代碼示例

- (void)testNSThread{
    _lock = [[NSLock alloc] init];
    _condition = [[NSCondition alloc] init];
    self.total = 20;

NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread1 setName:@"Thread--1"];
[thread1 start];

NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread2 setName:@"Thread--2"];
[thread2 start];

// [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; // [self performSelectorInBackground:@selector(run) withObject:nil]; }

  • (void)run{ while (YES) {
      [self.lock lock];
      if(self.total >= 0){
          [NSThread sleepForTimeInterval:0.09];
          NSInteger count = 20 - self.total;
          NSLog(@"total is :%zd,left is:%zd, thread name is:%@", self.total, count,[[NSThread currentThread] name]);
          self.total--;
      }else{
          break;
      }
      [self.lock unlock];
    
    } }</code></pre>

    分析說明:

    NSThread中線程內存管理,是否循環引用,同步問題都需要手動管理。除了代碼中的lock還可以用@synchronized來簡化NSLock的使用。

    - (void)doSomeThing:(id)anObj 
    { 
      @synchronized(anObj) 
      { 
    
      // Everything between the braces is protected by the @synchronized directive. 
    
    } }</code></pre>

    還可以用NSCondition中的wait消息設置等待線程,然后通過signal消息 發送信號的方式,在一個線程喚醒另外一個線程的等待。

    線程間通信:

    //在指定線程上執行操作
    //在主線程上執行操作
    [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 
    [self performSelector:@selector(run) onThread:threadName withObject:nil waitUntilDone:YES]; 
    //在當前線程執行操作
    [self performSelector:@selector(run) withObject:nil];

    獲取線程:

    NSThread *current = [NSThread currentThread];
    NSThread *main = [NSThread mainThread];

    Cocoa NSOperation

    使用方式

    NSOperation的使用方式有兩種:

    • 使用NSInvocationOperation和NSBlockOperation
    • 自定義NSOperation子類。

      代碼示例

    -(void)testNSOPeration{
      self.total = 20;
      NSInvocationOperation operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
      NSOperationQueue queue = [[NSOperationQueue alloc] init];
      [queue addOperation:operation1];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    [self run2];
}];

// [operation1 start];//當前線程直接執行 [operation2 addDependency:operation1]; // [operation2 addExecutionBlock:^{ // [self run]; // }]; [queue addOperation:operation2]; // queue.maxConcurrentOperationCount = 1; }

  • (void)run2{ while (YES) {
      if(self.total >= 0){
    
    // [NSThread sleepForTimeInterval:0.09];
          NSInteger count = 20 - self.total;
          NSLog(@"total is :%zd,left is:%zd, thread name is:%@", self.total, count,[[NSThread currentThread] name]);
          self.total--;
      }else{
          break;
      }
    
    } }</code></pre>

    自定義NSOperation子類

    自定義NSOperation子類需要實現start、main、isExecuting、isFinish、isConcurrnet(asynchronous)方法。

    @interface MyOperation : NSOperation {
      BOOL        executing;
      BOOL        finished;
    }
  • (void)completeOperation; @end

@implementation MyOperation

  • (id)init { self = [super init]; if (self) {

      executing = NO;
      finished = NO;
    

    } return self; }

  • (BOOL)isConcurrent { return YES; }

  • (BOOL)isExecuting { return executing; }

  • (BOOL)isFinished { return finished; } @end</code></pre>

    分析說明

    NSOperationQueue 是一個并行隊列,可以通過設置maxConcurrentOperationCount來設定最大并行操作數,對于自定義實現的NSOperation子類,可以通過實現isConcurrent(isAsynchronous)方法來制定具體的操作是否同步執行。

    GCD

    使用方式

    GCD的工作原理是讓程序平行排隊的特定任務,根據可用的處理資源,安排他們在任何可用的處理器核心上執行任務。

    隊列的創建

    • 主線程隊列(串行):
      dispatch_get_main_queue()
    • 全局隊列(并行):
      dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
      第一個參數說明隊列的優先級,第二個參數暫未用到,默認0。根據優先級劃分,共有四個全局隊列
      #define DISPATCH_QUEUE_PRIORITY_HIGH 2
      #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
      #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
      #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
    • 自定義隊列:
      dispatch_queue_create(queuename, attr)
      第一個參數制定隊列名稱,自定義隊列可以是串行也可以是并行的,由第二個參數attr決定。
      DISPATCH_QUEUE_SERIAL //(NULL) 串行
      DISPATCH_QUEUE_SERIAL_INACTIVE
      DISPATCH_QUEUE_CONCURRENT //并行
      多線程執行
    • 同步執行:
      dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block)
    • 異步執行:
      dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)
    • 一次性執行:

    tatic dispatch_once_t onceToken; dispatch_once(&amp;onceToken, ^{ code to be executed once });

    • 延時執行:

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ code to be executed after a specified delay });

    • 循環迭代執行:
      dispatch_apply(size_t iterations, dispatch_queue_t _Nonnull queue, ^(size_t)block)
      能并發地執行不同的迭代。這個函數是同步的,所以和普通的 for 循環一樣,它只會在所有工作都完成后才會返回。
    • 柵欄(barrier)執行:
      dispatch_barrier_sync(dispatch_queue_t _Nonnull queue, ^(void)block) dispatch_barrier_async(dispatch_queue_t _Nonnull queue, ^(void)block)
      柵欄執行的意思是:在barrier前面的任務執行結束后它才執行,而且它后面的任務等它執行完成之后才會執行。
    • 分組執行:
      dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block) dispatch_group_notify(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block)
      dispatch_group_async可以實現監聽一組任務是否完成,完成后得到通知執行其他的操作
      信號量
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
      dispatch_semaphore_signal(semaphore); 
      dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, kDefaultTimeoutLengthInNanoSeconds); dispatch_semaphore_wait(semaphore, timeoutTime)
      Dispatch Source
      dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatchQueue);
      dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, intervalInSeconds  NSEC_PER_SEC, leewayInSeconds  NSEC_PER_SEC);
      dispatch_source_set_event_handler(timer, ^{

      code to be executed when timer fires
      

      }); dispatch_resume(timer);</code></pre>

      代碼示例

      柵欄執行:</li> </ul>
      - (void)testBarrier{
        dispatch_queue_t queue = dispatch_queue_create("com.zyw.test", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{

        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async1");
      

      }); dispatch_async(queue, ^{

        [NSThread sleepForTimeInterval:2];
        NSLog(@"dispatch_async2");
      

      }); dispatch_barrier_async(queue, ^{

        NSLog(@"dispatch_barrier_async");
        [NSThread sleepForTimeInterval:2];
      
      

      }); dispatch_async(queue, ^{

        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async3");   
      

      }); }</code></pre>

      輸出:

      2016-12-02 17:17:24.710 TestThread[1434:164682] dispatch_async1
      2016-12-02 17:17:25.713 TestThread[1434:164681] dispatch_async2
      2016-12-02 17:17:25.713 TestThread[1434:164681] dispatch_barrier_async
      2016-12-02 17:17:28.720 TestThread[1434:164681] dispatch_async3

      分組執行:

      - (void)testGroup{
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, queue, ^{

        [NSThread sleepForTimeInterval:1];
        NSLog(@"group1");
      

      }); dispatch_group_async(group, queue, ^{

        [NSThread sleepForTimeInterval:2];
        NSLog(@"group2");
      

      }); dispatch_group_async(group, queue, ^{

        [NSThread sleepForTimeInterval:3];
        NSLog(@"group3");
      

      }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        NSLog(@"EndAllgroupTask");
      

      }); // 第二種使用方法 // dispatch_group_enter(group); // dispatch_group_leave(group); // dispatch_group_notify(group, dispatch_get_main_queue(), ^{ //
      // }); }</code></pre>

      輸出:

      2016-12-02 17:11:50.592 TestThread[1407:161853] group1
      2016-12-02 17:11:51.591 TestThread[1407:161852] group2
      2016-12-02 17:11:52.589 TestThread[1407:161855] group3
      2016-12-02 17:11:52.590 TestThread[1407:161635] EndAllgroupTask

      信號量:

      - (void)testSemaphore{
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
        for (NSInteger i = 0 ; i < 10; i++) {

        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"*****%zd", i);
            [NSThread sleepForTimeInterval:1];
            dispatch_semaphore_signal(semaphore);
        });
      

      } }</code></pre>

      輸出:

      2016-12-02 17:36:11.071 TestThread[1496:173204] *1
      2016-12-02 17:36:11.071 TestThread[1496:173207] *2
      2016-12-02 17:36:11.071 TestThread[1496:173203] *0
      2016-12-02 17:36:11.071 TestThread[1496:173242] *3
      2016-12-02 17:36:11.071 TestThread[1496:173243] *4

    2016-12-02 17:36:12.074 TestThread[1496:173244] *5 2016-12-02 17:36:12.074 TestThread[1496:173250] *9 2016-12-02 17:36:12.074 TestThread[1496:173246] *7 2016-12-02 17:36:12.074 TestThread[1496:173245] *6 2016-12-02 17:36:12.074 TestThread[1496:173247] *8</code></pre>

    Dispatch Source

    - (void)testSource{
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 5);
        dispatch_source_set_timer(timer, start, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"******Hello");
        });
        //啟動
        dispatch_resume(timer);
    }

    同步異步串行并行簡單常用未列出

    分析說明

    同步異步和并行串行

      并行隊列 串行隊列 主隊列
    異步執行 開啟多個新的線程,任務同時執行 開啟一個新的線程,任務按順序執行 不開啟新的線程,任務按順序執行
    同步執行 不開啟新的線程,任務按順序執行 不開啟新的線程,任務按順序執行 死鎖

    對于GCD中的block執行,都有對應的function執行函數,比如:

    dispatch_sync_f(dispatch_queue_t  _Nonnull queue, void * _Nullable context, dispatch_function_t  _Nonnull work)
    dispatch_async_f(dispatch_queue_t  _Nonnull queue, void * _Nullable context, dispatch_function_t  _Nonnull work)

    信號量

    dispatch_semaphore_create(N);N>=0時可以正常執行,當N小于0時一直等待

    代碼示例的解釋:創建了一個初使值為5的semaphore,每一次for循環都會創建一個新的線程(具體看GCD的線程池,此處認為是新建),線程結束的時候會發送一個信號,線程創建之前會信號等待,所以當同時創建了5個線程之后,for循環就會阻塞,等待有線程結束之后會增加一個信號才繼續執行,如此就形成了對并發的控制,如上就是一個并發數為5的一個線程隊列。

    Dispatch Source

    它基本上就是一個低級函數的 grab-bag,可監聽事件如下:

    DISPATCH_SOURCE_TYPE_DATA_ADD:屬于自定義事件,可以通過dispatch_source_get_data函數獲取事件變量數據,在我們自定義的方法中可以調用dispatch_source_merge_data函數向Dispatch Source設置數據,下文中會有詳細的演示。

    DISPATCH_SOURCE_TYPE_DATA_OR:屬于自定義事件,用法同上面的類型一樣。

    DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口發送事件。

    DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。

    DISPATCH_SOURCE_TYPE_PROC:與進程相關的事件。

    DISPATCH_SOURCE_TYPE_READ:讀文件事件。

    DISPATCH_SOURCE_TYPE_WRITE:寫文件事件。

    DISPATCH_SOURCE_TYPE_VNODE:文件屬性更改事件。

    DISPATCH_SOURCE_TYPE_SIGNAL:接收信號事件。

    DISPATCH_SOURCE_TYPE_TIMER:定時器事件。

    DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:內存壓力事件。

     

    來自:http://www.jianshu.com/p/af0a221896ba

     

 本文由用戶 JefferyGott 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!