iOS多線程

NSOperation

  • NSOperation有兩個常用子類用于創(chuàng)建線程操作:NSInvocationOperation和NSBlockOperation,兩種方式本質(zhì)沒有區(qū)別,但是是后者使用Block形式進(jìn)行代碼組織,使用相對方便。
-(void)loadImageWithMultiThread{
    /*創(chuàng)建一個調(diào)用操作
     object:調(diào)用方法參數(shù)
    */
    NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
    //創(chuàng)建完NSInvocationOperation對象并不會調(diào)用,它由一個start方法啟動操作,但是注意如果直接調(diào)用start方法,則此操作會在主線程中調(diào)用,一般不會這么操作,而是添加到NSOperationQueue中
//    [invocationOperation start];
    
    //創(chuàng)建操作隊列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    //注意添加到操作隊后,隊列會開啟一個線程執(zhí)行此操作
    [operationQueue addOperation:invocationOperation];
}
//直接使用操隊列添加操作
        [operationQueue addOperationWithBlock:^{
            [self loadImage:[NSNumber numberWithInt:i]];
        }];

        //創(chuàng)建多線程操作
        NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
            [self loadImage:[NSNumber numberWithInt:i]];
        }];
        //創(chuàng)建操作隊列

        [operationQueue addOperation:blockOperation];
  • 創(chuàng)建完NSInvocationOperation對象并不會調(diào)用,它由一個start方法啟動操作,但是注意如果直接調(diào)用start方法,則此操作會在主線程中調(diào)用,一般不會這么操作,而是添加到NSOperationQueue中
  • 每個NSOperation可以設(shè)置依賴線程:
    [blockOperation addDependency:lastBlockOperation];
  • 主線程更新UI
//更新UI界面,此處調(diào)用了主線程隊列的方法(mainQueue是UI主線程)
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self updateImageWithData:data andIndex:i];
    }];
  • 自定義NSOperation 參考
    • 自定義串行NSOperation
      如果是使用start調(diào)用制定串行的NSOperation,是串行執(zhí)行的,如果是吧自定義的串行NSOperation放到NSOperationQueue中則是并行的。
    • 自定義并行NSOperation
      • start方法:該方法必須實現(xiàn),
      • main:該方法可選,如果你在start方法中定義了你的任務(wù),則這個方法就可以不實現(xiàn),但通常為了代碼邏輯清晰,通常會在該方法中定 義自己的任務(wù)
      • isExecuting isFinished 主要作用是在線程狀態(tài)改變時,產(chǎn)生適當(dāng)?shù)腒VO通知
      • isConcurrent :必須覆蓋并返回YES;
  • NSOperationQueue創(chuàng)建的其他隊列同時具有串行、并發(fā)功能, maxConcurrentOperationCount,叫做最大并發(fā)數(shù)。
    • maxConcurrentOperationCount=-1時默認(rèn)是并發(fā)隊列。
    • maxConcurrentOperationCount>1時是并發(fā)隊列。
    • maxConcurrentOperationCount=1時是串行隊列
  • 如說有A、B兩個操作,其中A執(zhí)行完操作,B才能執(zhí)行操作,那么就需要讓B依賴于A。
    [op2 addDependency:op1]; // 讓op2 依賴于 op1,則先執(zhí)行op1,在執(zhí)行op2

GCD

  • 在GDC中一個操作是多線程執(zhí)行還是單線程執(zhí)行取決于當(dāng)前隊列類型和執(zhí)行方法,只有隊列類型為并行隊列并且使用異步方法執(zhí)行時才能在多個線程中執(zhí)行。
  • 串行隊列可以按順序執(zhí)行,并行隊列的異步方法無法確定執(zhí)行順序。
  • UI界面的更新最好采用同步方法,其他操作采用異步方法。
  • dispatch_get_main_queue(),這個是主線程,是Serial Dispatch Queue
  • dispatch_sync dispatch_async的區(qū)別
    dispatch_asycn函數(shù)的”asycn”意味著異步,就是將block追加到指定的queue中后,dispatch_asycn函數(shù)立即返回,不會等待執(zhí)行的結(jié)果。
dispatch_queue_t queue = dispatch_queue_create("com.example.myQueue",  DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"block開始  queue:%@", [NSThread currentThread]);
        for(int i=0; i++ ; i<100) {
            
        }
        NSLog(@"block結(jié)束  queue:%@", [NSThread currentThread]);
    });
    NSLog(@"主線程  queue:%@", [NSThread currentThread]);

打印結(jié)果為:

block開始  queue:<NSThread: 0x60800006aa00>{number = 3, name = (null)}
主線程  queue:<NSThread: 0x604000067bc0>{number = 1, name = main}
block結(jié)束  queue:<NSThread: 0x60800006aa00>{number = 3, name = (null)}

dispatch_sycn函數(shù)意味著同步,也就是將block函數(shù)追加到queue中后,在block任務(wù)執(zhí)行完之前,dispatch_sycn函數(shù)會一直等待。其實還是在主線程執(zhí)行的。

dispatch_queue_t queue = dispatch_queue_create("com.example.myQueue",  DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"block開始  queue:%@", [NSThread currentThread]);
        for(int i=0; i++ ; i<100) {}
        NSLog(@"block結(jié)束  queue:%@", [NSThread currentThread]);
    });
    NSLog(@"主線程  queue:%@", [NSThread currentThread]);

打印結(jié)果為:

block開始  queue:<NSThread: 0x6080000768c0>{number = 1, name = main}
block結(jié)束  queue:<NSThread: 0x6080000768c0>{number = 1, name = main}
主線程  queue:<NSThread: 0x6080000768c0>{number = 1, name = main}
  • 串行隊列 + 異步執(zhí)行 所有隊列中的事務(wù)都是在一個子線程中執(zhí)行
- (void)serialQueueaSync {
    NSLog(@"serialQueueSync begin-------%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("serialQueueSync", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            NSLog(@"1-------%@", [NSThread currentThread]);
        }});
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            NSLog(@"2-------%@", [NSThread currentThread]);
        }});
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            NSLog(@"3-------%@", [NSThread currentThread]);
        } });
    NSLog(@"serialQueueSync end-------%@", [NSThread currentThread]);
}

打印結(jié)果為:

serialQueueSync begin-------<NSThread: 0x60c0000628c0>{number = 1, name = main}
serialQueueSync end-------<NSThread: 0x60c0000628c0>{number = 1, name = main}
1-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
1-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
2-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
2-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
3-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
3-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
  • 串行隊列 + 同步執(zhí)行 所有隊列中的事務(wù)都是主線程執(zhí)行
- (void)serialQueueSync {
    NSLog(@"serialQueueSync begin-------%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("serialQueueSync", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            NSLog(@"1-------%@", [NSThread currentThread]);
        }});
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            NSLog(@"2-------%@", [NSThread currentThread]);
        } });
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            NSLog(@"3-------%@", [NSThread currentThread]);
        } });
    NSLog(@"serialQueueSync end-------%@", [NSThread currentThread]);
}

打印結(jié)果:

begin-------<NSThread: 0x608000060540>{number = 1, name = main}
1-------<NSThread: 0x608000060540>{number = 1, name = main}
1-------<NSThread: 0x608000060540>{number = 1, name = main}
2-------<NSThread: 0x608000060540>{number = 1, name = main}
2-------<NSThread: 0x608000060540>{number = 1, name = main}
3-------<NSThread: 0x608000060540>{number = 1, name = main}
3-------<NSThread: 0x608000060540>{number = 1, name = main}
 serialQueueSync end-------<NSThread: 0x608000060540>{number = 1, name = main}
  • 死鎖
    在串行隊列中執(zhí)行同步操作會發(fā)生死鎖,例如在主線程中執(zhí)行如下代碼,會在打印完第一個log后,死鎖。
    NSLog(@"hello world0");
    //因為是同步執(zhí)行,所以
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"hello world1");
    });
    NSLog(@"hello world2");

同理,如下代碼也會發(fā)生死鎖,如下代碼只會影響serialQueue內(nèi)部發(fā)生死鎖,不會阻塞主線程。

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", NULL);
    NSLog(@"main begin");
    dispatch_async(serialQueue, ^{
        NSLog(@"外層begin");
        dispatch_sync(serialQueue, ^{
            NSLog(@"hello world");
        });
        NSLog(@"外層end");
        
    });
    NSLog(@"main end");

運(yùn)行結(jié)果:

main begin
main end
外層begin
  • dispatch group 經(jīng)常會碰到這種情況,想要在加入到Dispatch Queue中的多個block任務(wù)都執(zhí)行完后取執(zhí)行其他任務(wù),如果使用的是串行隊列,只要將所有的block任務(wù)加入到串行隊列并在最后追加其他任務(wù)即可;如果使用的是并行隊列或者有多個DIspatch Queue時,可以使用Dispatch Group。
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, ^{
        NSLog(@"block1 begin");
        for(int i=0; i<1000; i++){}
        NSLog(@"block1 end");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block2 begin");
        for(int i=0; i<1000; i++){}
        NSLog(@"block1 end");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block3 begin");
        for(int i=0; i<1000; i++){}
        NSLog(@"block3 end");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue()/*queue*/, ^{
        NSLog(@"done");
    });
    NSLog(@"testGroup END");

打印結(jié)果

block1 begin
testGroup END
block2 begin
block3 begin
block1 end
block3 end
block1 end
done

dispatch_group_notify(dispatch_group_t group, <#dispatch_queue_t queue#>, <#^(void)block#>)是等到之前與group聯(lián)系過的block執(zhí)行完后會把這個block提交到queue中。
無論向什么樣的Dispatch Queue中追加任務(wù),使用Dispatch Group都可以監(jiān)視這些任務(wù)的結(jié)束。

  • dispatch_barrier_asycn
    dispatch_barrier_asycn會等到追加到queue中的block任務(wù)執(zhí)行完后,再將指定的block任務(wù)追加到queue中,然后等到指定的block執(zhí)行完后,queue才恢復(fù)一般的動作,之后追加的block任務(wù)才開始執(zhí)行。
NSMutableArray * array = @[@"1"].mutableCopy;
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"block1 = %@", array[0]);
        //讀取處理block1
    });
    dispatch_async(queue, ^{
        NSLog(@"block2 = %@", array[0]);
        
        //讀取處理block2
    });
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"block3 = %@", array[0]);
        //讀取處理block3
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch barrier begin");
        [array replaceObjectAtIndex:0 withObject:@"2"];
        NSLog(@"dispatch barrier end");
    });
    dispatch_async(queue, ^{
        NSLog(@"block4 = %@", array[0]);
    });

dispatch_barrier_async會等上面的block1, bolock2,block3 執(zhí)行完成后,再執(zhí)行自己的block,然后才是執(zhí)行block4,運(yùn)行結(jié)果:

block2 = 1
block1 = 1
block3 = 1
dispatch barrier begin
dispatch barrier end
block4 = 2
  • dispatch_after
    dispatch_after函數(shù)并不是在指定的時間后執(zhí)行block,而是在指定的時間將block添加到queue中。

線程同步

  • @synchronized代碼塊
    使用@synchronized解決線程同步問題相比較NSLock要簡單一些,日常開發(fā)中也更推薦使用此方法。首先選擇一個對象作為同步對象(如果成員變量或者屬性為同步對象,一般使用self),然后將”加鎖代碼”(爭奪資源的讀取、修改代碼)放到代碼塊中。@synchronized中的代碼執(zhí)行時先檢查同步對象是否被另一個線程占用,如果占用該線程就會處于等待狀態(tài),直到同步對象被釋放。
  • 信號量機(jī)制dispatch_semaphore 淺談GCD中的信號量,條件鎖NSCondition
    • 轉(zhuǎn)換異步block為同步方式運(yùn)行
  • 互斥鎖 NSLock、遞歸鎖NSRecursiveLock:同一線程可多次加鎖,不會造成死鎖.
  • 自旋鎖 OSSpinLock。自旋鎖與互斥鎖有點(diǎn)類似,只是自旋鎖不會引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖,"自旋"一詞就是因此而得名。由于自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠(yuǎn)高于互斥鎖。不再安全的 OSSpinLock
  • noatomic 只對set get方法起作用,
  • pthread_mutex(YYCache)

除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的。有消息稱,蘋果在新系統(tǒng)中已經(jīng)優(yōu)化了 pthread_mutex 的性能,所以它看上去和 OSSpinLock 差距并沒有那么大了。

參考文檔

GCD常用方法總結(jié)
起底多線程同步鎖(iOS)
NSRecursiveLock遞歸鎖的使用
iOS多線程-各種線程鎖的簡單介紹

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容