多線程

iOS中的多線程解決方案:

  • Pthreads
  • NSThread
  • GCD
  • NSOperation & NSOperationQueue

Pthreads

基于C語言,在類Unix操作系統(tǒng)(Unix、Linux、Mac OS X等)中,都可以使用。

(1)實現(xiàn)必須導(dǎo)入頭文件

#import <pthread.h>

(2)然后創(chuàng)建線程,并執(zhí)行任務(wù)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    pthread_t thread;
    //創(chuàng)建一個線程并自動執(zhí)行
    pthread_create(&thread, NULL, start, NULL);
}

void *start(void *data) {
    NSLog(@"%@", [NSThread currentThread]);

    return NULL;
}

由于是C語言函數(shù),所以需要自己管理線程的生命周期(切換、銷毀)

NSThread

Apple封裝之后,面向?qū)ο?,但是仍然需要手動管理生命周期。但可以獲得當前線程的各種屬性,比[NSThread currentThread],用于調(diào)試很方便。

  • 創(chuàng)建線程,然后開啟線程
  // 創(chuàng)建
  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];

  // 啟動
  [thread start];
  • 創(chuàng)建并自動開啟線程
  [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
  • performSelector形式創(chuàng)建線程
  [self performSelectorInBackground:@selector(run:) withObject:nil];
其他作用
//取消線程
- (void)cancel;

//啟動線程
- (void)start;

//判斷某個線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//設(shè)置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//獲取當前線程信息
+ (NSThread *)currentThread;

//獲取主線程信息
+ (NSThread *)mainThread;

//使當前線程暫停一段時間,或者暫停到某個時刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

GCD

使用了Block,系統(tǒng)自動管理生命周期。

  • 任務(wù):每一個Block就是一個任務(wù)(你想要做什么)。
  • 隊列:用于存放任務(wù)。
任務(wù)有兩種執(zhí)行方式:

同步執(zhí)行:會阻塞當前線程,必須等Block完,才能繼續(xù)往下執(zhí)行。

異步執(zhí)行:不會阻塞當前線程,程序不必等待,會直接繼續(xù)執(zhí)行。

隊列也有兩種:

串行隊列:按順序執(zhí)行,即先進入的先執(zhí)行,后進入的后執(zhí)行。

并行隊列:同時執(zhí)行任務(wù),沒有執(zhí)行順序。

注意:異步操作才會開辟新線程。串行隊列按順序執(zhí)行任務(wù),并行隊列同時執(zhí)行多個任務(wù)。

創(chuàng)建隊列
  • 主隊列:不會開辟新線程,串行執(zhí)行任務(wù)。
  dispatch_queue_t queue = ispatch_get_main_queue();
  • 全局隊列:并行隊列。
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  • 創(chuàng)建串行和并行隊列。
  //串行隊列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
  //并行隊列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
創(chuàng)建任務(wù)
  • 同步
  dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });
  • 異步
  dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });
總結(jié)效果:
各個隊列總結(jié)表

注意:
異步添加串行隊列,只會開啟一個新線程;

異步和同步?jīng)Q定是否開啟新線程,并行和串行決定任務(wù)的執(zhí)行順序;

隊列組

隊列組可以將很多隊列添加到一個組里,這樣做的好處是,當這個組里所有的任務(wù)都執(zhí)行完了,隊列組會通過一個方法通知我們

//1.創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//3.多次使用隊列組的方法執(zhí)行任務(wù), 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"group-01 - %@", [NSThread currentThread]);
    }
});

//3.2.主隊列執(zhí)行8次循環(huán)
dispatch_group_async(group, dispatch_get_main_queue(), ^{
    for (NSInteger i = 0; i < 8; i++) {
        NSLog(@"group-02 - %@", [NSThread currentThread]);
    }
});

//3.3.執(zhí)行5次循環(huán)
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"group-03 - %@", [NSThread currentThread]);
    }
});

//4.都完成后會自動通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"完成 - %@", [NSThread currentThread]);
});

NSOperation和NSOperationQueue

NSOperation 是蘋果公司對 GCD 的封裝,完全面向?qū)ο?,所以使用起來更好理解?大家可以看到NSOperationNSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊列 。

1.將要執(zhí)行的任務(wù)封裝到一個 NSOperation 對象中。

2.將此任務(wù)添加到一個 NSOperationQueue 對象中。

添加任務(wù)

NSOperation 只是一個抽象類,所以不能封裝任務(wù)。但它有 2 個子類用于封裝任務(wù)。分別是:NSInvocationOperationNSBlockOperation 。創(chuàng)建一個 Operation 后,需要調(diào)用 start 方法來啟動任務(wù),它會默認在當前隊列同步執(zhí)行。當然你也可以在中途取消一個任務(wù),只需要調(diào)用其 cancel方法即可。

  • NSInvocationOperation : 需要傳入一個方法名。
 //1.創(chuàng)建NSInvocationOperation對象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

  //2.開始執(zhí)行
  [operation start];
  • NSBlockOperation
  //1.創(chuàng)建NSBlockOperation對象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

  //2.開始任務(wù)
  [operation start];

之前說過這樣的任務(wù),默認會在當前線程執(zhí)行。但是 NSBlockOperation還有一個方法:addExecutionBlock:,通過這個方法可以給 Operation 添加多個執(zhí)行 Block。這樣 Operation 中的任務(wù)會并發(fā)執(zhí)行,它會在主線程和其它的多個線程 執(zhí)行這些任務(wù)。

//1.創(chuàng)建NSBlockOperation對象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

  //添加多個Block
  for (NSInteger i = 0; i < 5; i++) {
      [operation addExecutionBlock:^{
          NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
      }];
  }

  //2.開始任務(wù)
  [operation start];
創(chuàng)建隊列

看過上面的內(nèi)容就知道,我們可以調(diào)用一個 NSOperation 對象的 start() 方法來啟動這個任務(wù),但是這樣做他們默認是 同步執(zhí)行 的。就算是 addExecutionBlock 方法,也會在當前線程和其他線程中執(zhí)行,也就是說還是會占用當前線程。這時就要用到隊列 NSOperationQueue了。而且,按類型來說的話一共有兩種類型:主隊列、其他隊列。只要添加到隊列,會自動調(diào)用任務(wù)的 start() 方法。

  • 主隊列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 其他隊列
//1.創(chuàng)建一個其他隊列    
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//2.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//3.添加多個Block
for (NSInteger i = 0; i < 5; i++) {
    [operation addExecutionBlock:^{
        NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
    }];
}

//4.隊列添加任務(wù)
[queue addOperation:operation];
  • NSOperationQueue 有一個參數(shù)maxConcurrentOperationCount (最大并發(fā)數(shù)),用來設(shè)置最多可以讓多少個任務(wù)同時執(zhí)行。當你把它設(shè)置為 1 的時候,就是串行。

  • 添加任務(wù)的方法:- (void)addOperationWithBlock:(void (^)(void))block;

  • 添加依賴

//1.任務(wù)一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下載圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任務(wù)二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任務(wù)三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.設(shè)置依賴
[operation2 addDependency:operation1];      //任務(wù)二依賴任務(wù)一
[operation3 addDependency:operation2];      //任務(wù)三依賴任務(wù)二

//5.創(chuàng)建隊列并加入任務(wù)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

注意:

(1)不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。

(2)可以使用 removeDependency 來解除依賴關(guān)系。

(3)可以在不同的隊列之間依賴,反正就是這個依賴是添加到任務(wù)身上的,和隊列沒關(guān)系。

其他方法
  • NSOperation
BOOL executing; //判斷任務(wù)是否正在執(zhí)行

BOOL finished; //判斷任務(wù)是否完成

void (^completionBlock)(void); //用來設(shè)置完成后需要執(zhí)行的操作

- (void)cancel; //取消任務(wù)

- (void)waitUntilFinished; //阻塞當前線程直到此任務(wù)執(zhí)行完畢
  • NSOperationQueue
NSUInteger operationCount; //獲取隊列的任務(wù)數(shù)

- (void)cancelAllOperations; //取消隊列中所有的任務(wù)

- (void)waitUntilAllOperationsAreFinished; //阻塞當前線程直到此隊列中的所有任務(wù)執(zhí)行完畢

[queue setSuspended:YES]; // 暫停queue

[queue setSuspended:NO]; // 繼續(xù)queue

其他

線程同步
  • 同步鎖,即資源競爭:多條線程同時訪問同一資源,造成的數(shù)據(jù)安全問題,通過加鎖保證每次只有一個線程訪問資源。
@synchronized(self) {
    //需要執(zhí)行的代碼塊
}
  • 同步執(zhí)行:把多個線程都要執(zhí)行此段代碼添加到同一個串行隊列,這樣就實現(xiàn)了線程同步的概念。
//GCD
  //需要一個全局變量queue,要讓所有線程的這個操作都加到一個queue中
  dispatch_sync(queue, ^{
      NSInteger ticket = lastTicket;
      [NSThread sleepForTimeInterval:0.1];
      NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
      ticket -= 1;
      lastTicket = ticket;
  });


  //NSOperation & NSOperationQueue
  //重點:1. 全局的 NSOperationQueue, 所有的操作添加到同一個queue中
  //       2. 設(shè)置 queue 的 maxConcurrentOperationCount 為 1
  //       3. 如果后續(xù)操作需要Block中的結(jié)果,就需要調(diào)用每個操作的waitUntilFinished,阻塞當前線程,一直等到當前操作完成,才允許執(zhí)行后面的。waitUntilFinished 要在添加到隊列之后!

  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSInteger ticket = lastTicket;
      [NSThread sleepForTimeInterval:1];
      NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
      ticket -= 1;
      lastTicket = ticket;
  }];

  [queue addOperation:operation];

  [operation waitUntilFinished];

  //后續(xù)要做的事
延遲執(zhí)行
  • perform
  // 3秒后自動調(diào)用self的run:方法,并且傳遞參數(shù):@"abc"
  [self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
  • GCD
// 創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 設(shè)置延時,單位秒
double delay = 3; 

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
    // 3秒后需要執(zhí)行的任務(wù)
});
  • NSTimer
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];
單例
@interface Tool : NSObject <NSCopying>

+ (instancetype)sharedTool;

@end

@implementation Tool

static id _instance;

+ (instancetype)sharedTool {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[Tool alloc] init];
    });

    return _instance;
}

@end
從其他線程返回主線程
  • NSThread
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
  • GCD
dispatch_async(dispatch_get_main_queue(), ^{

});
  • NSOperationQueue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{

}];
幾道關(guān)于線程死鎖的例題

多線程死鎖例題

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

相關(guān)閱讀更多精彩內(nèi)容

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