工作總結之多線程

一、一些基本概念

1、線程狀態

線程狀態

注:線程的死亡狀態代表線程任務執行完畢,正常退出。或者手動調用[NSThread exit]手動退出。

2、線程同步和異步

二者的根本區別在于有沒有開辟新線程的能力,同步會阻塞當前線程,異步不會阻塞當前線程。但是使用異步不一定開辟了新的線程,如下表:

區別 并發隊列 串行隊列 主隊列
同步 沒有開啟新線程,串行執行任務 沒有開啟新線程,串行執行任務 主線程調用:死鎖卡住不執行。其他線程調用:沒有開啟新線程,串行執行任務
異步 有開啟新線程,并發執行任務 有開啟新線程(1條),串行執行任務 沒有開啟新線程,串行執行任務

3、并行和串行

并行是指隊列中的任務執行順序不固定,單核CPU下,通過調度算法分片執行,多核CPU下,任務可以同時執行。

串行是指隊列中的任務按照添加順序執行

4、線程安全

多個線程操作同一塊資源的時候,為了避免數據發生混亂,需要注意線程安全。可以通過加鎖解決:

  • @synchronized(){},互斥鎖。
  • NSLock
  • NSConditionLock 條件鎖 可以設置條件
#import "NSLockTest.h"
@interface NSLockTest()
@property (nonatomic,strong) NSMutableArray *tickets;
@property (nonatomic,assign) int soldCount;
@property (nonatomic,strong) NSConditionLock *condition;
@end
@implementation NSLockTest
- (void)forTest
{
    self.tickets = [NSMutableArray arrayWithCapacity:1];
    self.condition = [[NSConditionLock alloc]initWithCondition:0];
    NSThread *windowOne = [[NSThread alloc]initWithTarget:self selector:@selector(soldTicketOne) object:nil];
    [windowOne start];
    
    NSThread *windowTwo = [[NSThread alloc]initWithTarget:self selector:@selector(soldTicketTwo) object:nil];
    [windowTwo start];
   
    NSThread *windowTuiPiao = [[NSThread alloc]initWithTarget:self selector:@selector(tuiPiao) object:nil];
    [windowTuiPiao start];

}
//一號窗口
-(void)soldTicketOne
{
    while (YES) {
        NSLog(@"====一號窗口沒票了,等別人退票");
        [self.condition lockWhenCondition:1];
        NSLog(@"====在一號窗口買了一張票,%@",[self.tickets objectAtIndex:0]);
        [self.tickets removeObjectAtIndex:0];
        [self.condition unlockWithCondition:0];
    }
}
//二號窗口
-(void)soldTicketTwo
{
    while (YES) {
        NSLog(@"====二號窗口沒票了,等別人退票");
        [self.condition lockWhenCondition:2];
        NSLog(@"====在二號窗口買了一張票,%@",[self.tickets objectAtIndex:0]);
        [self.tickets removeObjectAtIndex:0];
        [self.condition unlockWithCondition:0];
    }
}
- (void)tuiPiao
{
    while (YES) {
        sleep(3);
        [self.condition lockWhenCondition:0];
        [self.tickets addObject:@"南京-北京(退票)"];
        int x = arc4random() % 2;
        if (x == 1) {
            NSLog(@"====有人退票了,趕快去一號窗口買");
            [self.condition unlockWithCondition:1];
        }else
        {
            NSLog(@"====有人退票了,趕快去二號窗口買");
            [self.condition unlockWithCondition:2];
        }
    }
    
}
@end
  • NSRecursiveLock 遞歸鎖
//普通線程鎖,會造成死鎖
    NSLock *lock = [[NSLock alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^block)(int);
        block = ^(int value) {
            //加鎖
            [lock lock];
            if (value > 0) {
                NSLog(@"%d",value);
                sleep(2);
                //遞歸調用
                block(--value);
            }
            //解鎖
            [lock unlock];
        };
        //調用代碼塊
        block(10);
    });
//遞歸鎖,不會造成死鎖
    NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^block)(int);
        block = ^(int value) {
            //加鎖
            [lock lock];
            if (value > 0) {
                NSLog(@"%d",value);
                sleep(2);
                //遞歸調用
                block(--value);
            }
            //解鎖
            [lock unlock];
        };
        //調用代碼塊
        block(10);
    });
  • dispatch_semaphore:性能第二好
  • pthread_mutex:互斥鎖,性能最好
pthread_mutex_t _lock;
- (NSDictionary *)framePropertiesAtIndex:(NSUInteger)index {
    NSDictionary *result = nil;
    pthread_mutex_lock(&_lock);
    result = [self _framePropertiesAtIndex:index];
    pthread_mutex_unlock(&_lock);
    return result;
}

5、線程通訊

兩種情況:

  • 子線程切換到主線程
dispatch_async(dispatch_get_main_queue(), ^{ 
  // 回到主線程,執行UI刷新操作 
}

[self performSelectorOnMainThread:@selector(displayImage:) withObject:image waitUntilDone:YES];
[self performSelector:@selector(displayImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // 回到主線程,執行UI刷新操作 
}];
  • 主線程切換到子線程

6、線程和進程的關系

二、performSelector方法集

1、

這三個方法,均為同步執行,與線程無關,主線程和子線程中均可調用成功。等同于直接調用該方法。在需要動態的去調用方法的時候去使用。
例如:[self performSelector:@selector(test2)];與[self test2];執行效果上完全相同。

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

2、

這兩個方法為異步執行,即使delay傳參為0,仍為異步執行。只能在主線程中執行,在子線程中不會調到aSelector方法。可用于當點擊UI中一個按鈕會觸發一個消耗系統性能的事件,在事件執行期間按鈕會一直處于高亮狀態,此時可以調用該方法去異步的處理該事件,就能避免上面的問題。

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;

在方法未到執行時間之前,取消方法為:

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

注意:調用該方法之前或在該方法所在的viewController生命周期結束的時候去調用取消函數,以確保不會引起內存泄露。

3、

這兩個方法,在主線程和子線程中均可執行,均會調用主線程的aSelector方法;
如果設置wait為YES:等待當前線程執行完以后,主線程才會執行aSelector方法;
設置為NO:不等待當前線程執行完,就在主線程上執行aSelector方法。
如果,當前線程就是主線程,那么aSelector方法會馬上執行。
注意:apple不允許程序員在主線程以外的線程中對ui進行操作,此時我們必須調用performSelectorOnMainThread函數在主線程中完成UI的更新。

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

4、

調用指定線程中的某個方法。分析效果同3。

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

5、

開啟子線程在后臺運行

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

三、NSThread

1、創建NSThread線程的方法

  • 第一種方法:
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(task) object:nil];
    [thread setName:@"創建線程,需要調用 start 方法"];
    [thread start];
  • 第二種方法:
[NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
  • 第三種方法:
[self performSelectorInBackground:@selector(task) withObject:nil];
  • 第四種方法:

創建線程對象

// 開了子線程,但是沒有任務,需要告訴它任務是什么
// 自定義類,繼承NSThread
// 重寫main方法,在main方法里面封裝任務
NSThread *thread = [[CustomThread alloc] init];
[thread setName:@"創建自定義線程,需要調用 start 方法"];
[thread start];

總結:如果你想拿到線程對象,使用第一種或者第四種方法,但是需要手動調用start。如果想創建自啟線程,使用第二種或者第三種方法,但是拿不到線程對象。

2、踩坑1

  • [thread cancel]和[NSThread exit];
    • 調用實例對象的- (void)cancel;方法、并不會直接取消線程。其作用只是對應線程的cancel屬性設置為YES、線程依舊繼續執行。在此之后:
      • thread.cancel為YES。
      • thread.executing為YES。
      • thread.finished為NO。(除非全執行完了)
    • 我們需要在適當的位置執行[NSThread exit];才可以真正關閉線程、余下的代碼不會繼續執行。同時:
      • thread.cancel為你設置的狀態(默認應該是NO)。
      • thread.executing為NO。
      • thread.finished為YES。

3、最大并發數

- (void)concurrentNumber {
    for (int a = 0; a < 1000; a ++) {
        [self performSelectorInBackground:@selector(cn:) withObject:[NSNumber numberWithInt:a]];
    }
}

- (void)cn:(NSNumber *)number {
    NSLog(@"%@%@", number, [NSThread currentThread]);
}

log結果在1-1001不等,可以確定的是使用NSThread不支持限制最大并發數,但是如果超過某個閾值會error[NSThread start]: Thread creation failed with error 35)

4、Example

#import "NSThreadViewController.h"

@interface NSThreadViewController ()

@property (nonatomic, strong) UIImageView *imageView;

@property (nonatomic, assign) NSInteger ticketCount;

@end

@implementation NSThreadViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 100, 300, 200)];
    [self.view addSubview:self.imageView];
    [self test_six];
}

// 顯式創建線程,此方法需要調用 start 方法啟動線程,需要管理生命周期
- (void)test_one {
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(task) object:nil];
    [thread setName:@"創建線程,需要調用 start 方法"];
    [thread start];
}

- (void)test_two {
    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
}

- (void)test_three {
    [self performSelectorInBackground:@selector(task) withObject:nil];
}

// 線程阻塞
- (void)test_four {
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(task_preventThread) object:nil];
    [thread start];
}

- (void)task {
    [NSThread sleepForTimeInterval:2];
    NSLog(@"%@", [NSThread currentThread]);
}

- (void)task_preventThread {
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
    NSLog(@"%@", [NSThread currentThread]);
    NSLog(@"%d", [NSThread isMainThread]);
    NSLog(@"%@", [NSThread mainThread]);
}

// 下載圖片

- (void)test_five {
    [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:@"https://ysc-demo-1254961422.file.myqcloud.com/YSC-phread-NSThread-demo-icon.jpg"];
}

- (void)downloadImage:(NSString *)urlString {
    NSURL *url = [NSURL URLWithString:urlString];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];
    [self performSelectorOnMainThread:@selector(displayImage:) withObject:image waitUntilDone:YES];
}

- (void)displayImage:(UIImage *)image {
    self.imageView.image = image;
}

// 線程同步
- (void)test_six {
    self.ticketCount = 50;
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
    thread1.name = @"線程1";
    [thread1 start];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
    thread2.name = @"線程2";
    [thread2 start];
}

- (void)sellTicket {
    while (1) {
        @synchronized (self) {
            if (self.ticketCount > 0) {
                self.ticketCount --;
                NSLog(@"當前線程:%@,剩余票數:%ld", [NSThread currentThread].name, self.ticketCount);
            } else {
                NSLog(@"所有的票已經賣完");
                break;
            }
        }
    }
}

@end

四、NSOperation

1、自定義非并發 NSOperation

  • 要點:重寫 main 函數,創建自動釋放池,如果操作未取消,就執行操作
#import <Foundation/Foundation.h>

@interface ZYOperation : NSOperation

@end


#import "ZYOperation.h"

@implementation ZYOperation

- (void)main {
    // 這里獲取不到主線程的自動釋放池,所以創建自動釋放池自動釋放內部創建的對象
    @autoreleasepool {
        if (!self.isCancelled) {
            // 這里寫上任務代碼
            NSLog(@"%@", [NSThread currentThread]);
        }
    }
}

@end

2、自定義并發 NSOperation

  • 要點:
    • 定義 finishen、executing 實例變量,在 init 方法中初始化其值,并重寫 isConcurrent(必須重寫)、isFinished、isExecuting、start、main 方法。
    • 在 start 方法中任務判斷是否被取消,如果已取消,手動出發 isFinished 的KVO,否則手動出發 Executing 的KVO,并使用 NSThread 后臺執行 main 函數。
#import <Foundation/Foundation.h>

@interface ZYConcurrentOperation : NSOperation {
    BOOL executing;
    BOOL finished;
}

@end


#import "ZYConcurrentOperation.h"

@implementation ZYConcurrentOperation

- (instancetype)init {
    self = [super init];
    if (self) {
        finished = NO;
        executing = NO;
    }
    return self;
}

- (BOOL)isFinished {
    return finished;
}

- (BOOL)isExecuting {
    return executing;
}

- (BOOL)isConcurrent {
    return YES;
}

- (void)start {
    if (self.isCancelled) {
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    
    [self willChangeValueForKey:@"isExecuting"];
    executing = YES;
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    [self didChangeValueForKey:@"isExecuting"];
}

- (void)main {
    @autoreleasepool {
        // 這里執行自定義任務
        NSLog(@"%@", [NSThread currentThread]);
        
        [self willChangeValueForKey:@"isExecuting"];
        [self willChangeValueForKey:@"isFinished"];
        finished = NO;
        executing = YES;
        [self didChangeValueForKey:@"isExecuting"];
        [self didChangeValueForKey:@"isFinished"];
    }
}

@end

3、創建的Operation是不是多線程執行的實驗:

    // 多線程
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task) object:nil];
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation];
    // 這里 block 會被回調
    [operation setCompletionBlock:^{
        NSLog(@"操作執行完成");
    }];

    // 多線程
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task_1) object:nil];
    operation1.queuePriority = NSOperationQueuePriorityLow;
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task_2) object:nil];
    operation2.queuePriority = NSOperationQueuePriorityHigh;
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];

    // 非多線程
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
    [operation start];

    // 添加的操作數大于1時,可能是多線程
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
    [operation start];

    // 多線程
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation];

4、依賴關系

  • 依賴必須在操作被添加到隊列(確切來說應該是被執行)之前設置、否則無效
  • 這個例子說明 NSOperationQueue 不同于 GCD 中隊列 FIFO 的規則,隊列中的執行順序與依賴關系和優先級有關
  • 取消依賴關系[operation1 removeDependency:operation0];
  • 操作的依賴關系與本身綁定、并不受限于同一個隊列。即使所執行的隊列不同、也可以完成依賴操作
    NSBlockOperation *operation0 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@,0", [NSThread currentThread]);
    }];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@,1", [NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@,2", [NSThread currentThread]);
        NSLog(@"%@", NSOperationQueue.currentQueue);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@,3", [NSThread currentThread]);
        NSLog(@"%@", NSOperationQueue.mainQueue);
    }];
    [operation1 addDependency:operation0];
    [operation2 addDependency:operation1];
    [operation3 addDependency:operation2];
    NSOperationQueue *queue = [NSOperationQueue new];
    [queue addOperation:operation0];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];

5、依賴關系和優先級的判定

  • 先判定依賴關系,再執行優先級
  • 下面代碼先執行任務3,再執行高優先級任務,最后執行低優先級任務
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    
    operationQueue.maxConcurrentOperationCount = 1;
    
    NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"低優先級任務");
    }];
    blockOperation1.queuePriority = NSOperationQueuePriorityLow;
    
    NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"高優先級任務");
        sleep(1);
    }];
    blockOperation2.queuePriority = NSOperationQueuePriorityHigh;
    
    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務3");
    }];
    
    [blockOperation1 addDependency:blockOperation3];
    [blockOperation2 addDependency:blockOperation3];
    
    [operationQueue addOperation:blockOperation1];
    [operationQueue addOperation:blockOperation2];
    [operationQueue addOperation:blockOperation3];

6、依賴在添加進隊列之后雖然不能追加。但是可以對某操作進行追加addExecutionBlock、也可以延后操作的執行。

    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    
    NSBlockOperation * blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"進入操作1");
        sleep(3);
        NSLog(@"操作1完成");
    }];
    
    NSBlockOperation * blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"進入依賴操作");
    }];
    
    [blockOperation2 addDependency:blockOperation1];
    
    [operationQueue addOperation:blockOperation1];
    [operationQueue addOperation:blockOperation2];
    
    [blockOperation1 addExecutionBlock:^{
        NSLog(@"進入追加操作");
        sleep(5);
        NSLog(@"追加操作完成");
    }];

7、串行和并行的控制

  • 通過maxConcurrentOperationCount,如果設置為1,則為串行,大于1,為并行
  • maxConcurrentOperationCount值默認為-1,不指定其值得情況下,默認為并行,指定其值為0,則不會執行queue中添加的操作
    NSBlockOperation *operation0 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@,0", [NSThread currentThread]);
    }];
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@,1", [NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@,2", [NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@,3", [NSThread currentThread]);
    }];
    NSArray <NSOperation *>*operationArray = [NSArray arrayWithObjects:operation0, operation1, operation2, operation3, nil];
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 2;
    [queue addOperations:operationArray waitUntilFinished:NO];

8、任務數組阻塞和單個任務阻塞

  • 單個任務阻塞
    • 阻塞當前線程、直到該操作執行完成
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
        
        sleep(3);
        NSLog(@"操作3執行完畢");
    }];
    
    NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作2開始執行");
        [blockOperation3 waitUntilFinished];
        NSLog(@"操作2執行完畢");
    }];
    [operationQueue addOperation:blockOperation2];
    [operationQueue addOperation:blockOperation3];
  • 任務數組阻塞
    • 如果為YES。阻塞當前線程、直到隊列該次添加的所有操作全部執行完成。
    • 如果為NO。就是批量添加操作而已
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
        sleep(3);
        NSLog(@"操作3執行完畢");
    }];
    NSLog(@"添加操作");
    [operationQueue addOperations:@[blockOperation3] waitUntilFinished:YES];
    NSLog(@"添加完成");

9、主隊列([NSOperationQueue mainQueue])可以不可以修改最大并發數?主隊列下添加的操作、都會在主線程執行么?

  • 不能、主隊列的最大并發數始終為1(自定義隊列默認為-1)、且修改無效。
  • 默認狀況下是的、但也有例外(追加操作addExecutionBlock)。

10、NSOperationQueue的最大并發數

  • 我測試了一下,創建100個任務時,最大值為66。

11、NSOperation的優勢

  • 可添加完成的代碼塊,在操作完成后執行。
  • 添加操作之間的依賴關系,方便的控制執行順序。
  • 設定操作執行的優先級。
  • 可以很方便的取消一個操作的執行。
  • 使用 KVO 觀察對操作執行狀態的更改:isExecuteing、isFinished、isCancelled。

五、GCD

  • GCD:是 Apple 開發的一個多核編程的較新的解決方法。它主要用于優化應用程序以支持多核處理器。(來自百度百科)
  • 同步和異步:二者根本的區別是有沒有開辟新線程的能力,以及是否等待上一個任務執行完畢。順序執行和并發執行。
  • 隊列:隊列是一種特殊的線性表,采用 FIFO(先進先出)的原則,即新任務總是被插入到隊列的末尾,而讀取任務的時候總是從隊列的頭部開始讀取。每讀取一個任務,則從隊列中釋放一個任務

1、GCD 優勢

  • GCD 可用于多核的并行運算
  • GCD 會自動利用更多的 CPU 內核(比如雙核、四核)
  • GCD 會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
  • 程序員只需要告訴 GCD 想要執行什么任務,不需要編寫任何線程管理代碼

2、隊列的創建

// 主隊列:用來執行主線程上的操作任務
dispatch_queue_t mainQueue   = dispatch_get_main_queue();

// 創建串行隊列:第一個參數是隊列名稱,第二個參數是隊列類型(串行或并行)
dispatch_queue_t serialQueue = dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t  _Nullable attr#>);

// 創建并行隊列的兩種方式
// 1、第一個參數是隊列優先級,第二個參數是預留的,傳0即可
dispatch_queue_t concurrentQueue1 = dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>);
//2、第一個參數是隊列名稱,第二個參數是隊列類型(串行或并行)
dispatch_queue_t concurrentQueue2 = dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t  _Nullable attr#>);

3、GCD 的其他方法

柵欄函數:dispatch_barrier_async

dispatch_barrier_async函數會等待前邊追加到并發隊列中的任務全部執行完畢之后,再將指定的任務追加到該異步隊列中。然后在dispatch_barrier_async函數追加的任務執行完畢之后,異步隊列才恢復為一般動作,接著追加任務到該異步隊列并開始執行。

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任務 barrier
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當前線程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
}

GCD 延時執行方法:dispatch_after

- (void)after {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"asyncMain---begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0秒后異步追加任務代碼到主隊列,并開始執行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印當前線程
    });
}
// 需要注意的是,dispatch_after并不是在指定的時間之后執行處理,而是在指定的時間之后追加到dispatch queue處理。此源碼的作用是,在指定的時間之后,追加到main dispatch queue處理;

dispatch_once

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只執行1次的代碼(這里面默認是線程安全的)
    });
}

dispatch_group

  • NSOperation的添加進隊列后可不可以追加依賴?GCD任務組添加監聽后可不可以追加任務?
    • NSOperation的依賴必須在添加進隊列(并且執行前)之前設置。(但是我們可以對某被依賴的操作進行追加addExecutionBlock以延緩調用)

    • GCD任務組則具備追加任務的功能。前提是監聽并未被觸發。

    NSLog(@"..............start..............");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"第一組任務");
        [NSThread sleepForTimeInterval:1];
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"第二組任務");
        [NSThread sleepForTimeInterval:1];
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:1];
    });
    NSLog(@"...............end...............");
}
/*
 2019-02-23 19:48:35.942689+0800 Group[10209:459897] ..............start..............
 2019-02-23 19:48:35.942921+0800 Group[10209:459897] ...............end...............
 2019-02-23 19:48:35.943606+0800 Group[10209:459928] 第一組任務
 2019-02-23 19:48:35.943636+0800 Group[10209:459930] 第二組任務
 2019-02-23 19:48:36.947657+0800 Group[10209:459897] <NSThread: 0x600000a01340>{number = 1, name = main}
*/

- (void)test_2 {
    NSLog(@"..............start..............");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"第一組任務");
        [NSThread sleepForTimeInterval:1];
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"第二組任務");
        [NSThread sleepForTimeInterval:1];
    });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"...............end...............");
}
/*
 2019-02-23 19:47:56.637099+0800 Group[10196:459432] ..............start..............
 2019-02-23 19:47:56.637425+0800 Group[10196:459471] 第一組任務
 2019-02-23 19:47:56.637485+0800 Group[10196:459472] 第二組任務
 2019-02-23 19:47:57.639444+0800 Group[10196:459432] ...............end...............
*/

- (void)test_3 {
    NSLog(@"..............start..............");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"第一組任務");
        [NSThread sleepForTimeInterval:1];
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"第二組任務");
        [NSThread sleepForTimeInterval:1];
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:1];
    });
    NSLog(@"...............end...............");
}
/*
 19-02-23 19:43:17.207690+0800 Group[10144:457044] ..............start..............
 2019-02-23 19:43:17.207927+0800 Group[10144:457044] ...............end...............
 2019-02-23 19:43:17.207967+0800 Group[10144:457104] 第二組任務
 2019-02-23 19:43:17.207985+0800 Group[10144:457103] 第一組任務
 2019-02-23 19:43:18.212495+0800 Group[10144:457044] <NSThread: 0x60000177a900>{number = 1, name = main}
*/

GCD 信號量:dispatch_semaphore

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}
// 控制線程并發數1
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue",DISPATCH_QUEUE_SERIAL);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(4);
    for (NSInteger i = 0; i < 15; i++) {
        dispatch_async(serialQueue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_async(concurrentQueue, ^{
                NSLog(@"thread:%@開始執行任務%d",[NSThread currentThread],(int)i);
                sleep(1);
                NSLog(@"thread:%@結束執行任務%d",[NSThread currentThread],(int)i);
                dispatch_semaphore_signal(semaphore);});
        });
    }
/**
 * 線程安全:使用 semaphore 加鎖
 * 初始化火車票數量、賣票窗口(線程安全)、并開始賣票
 */
- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火車票售賣窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火車票售賣窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 * 售賣火車票(線程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        // 相當于加鎖
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  //如果還有票,繼續售賣
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { //如果已賣完,關閉售票窗口
            NSLog(@"所有火車票均已售完");
            
            // 相當于解鎖
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相當于解鎖
        dispatch_semaphore_signal(semaphoreLock);
    }
}

dispatch_set_target_queue

作用:變更生成的dispatch queue的優先級

dispatch_apply

快速迭代遍歷,多線程進行。
下面代碼end輸出是在dispatch_apply完成之后。

dispatch_queue_t queue =dispatch_queue_create("apply并行隊列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(count, queue, ^(size_t index) {
        NSLog(@"%@----%@",array[index],[NSThread currentThread]);
    });
NSLog(@"end");

四、GCD執行任務的順序

場景一

- (void)examine {
    NSLog(@"1"); // 任務1
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2"); // 任務2
    });
    NSLog(@"3"); // 任務3
}

// log
1

/*
*  主線程執行 examine 方法,相當于把 examine 放到了主線程的隊列當中
*  接下來遇到了同步執行,相當于把任務 2 放到了主線程的隊列當中
*  此時的情況是,任務 2 會等待 examine 執行完成,同時,由于是同步執行,examine 也會等待 任務 2 執行完成,形成死鎖
*  也可以這樣說,examine 函數中的任務 3 會等待 任務 2 執行完成,任務 2 會等待 examine 中的任務 3 執行完成,形成死鎖
*  導致程序崩潰
*/

場景二

- (void)examine {
    NSLog(@"1"); // 任務1
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"2"); // 任務2
    });
    NSLog(@"3"); // 任務3
}

// log:
1
2
3
/*
*  主線程執行 examine 方法,相當于把 examine 放到了主線程的隊列當中
*  接下來遇到同步任務,相當于把任務 2 放到了子隊列當中執行
*  任務 2 執行完成之后,回到主線程,繼續執行任務 3
*  不會造成死鎖
*/

場景三

- (void)examine {
    dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); // 任務1
    dispatch_async(queue, ^{
        NSLog(@"2"); // 任務2
        dispatch_sync(queue, ^{
            NSLog(@"3"); // 任務3
        });
        NSLog(@"4"); // 任務4
    });
    NSLog(@"5"); // 任務5
}

// log:
1
5
2
// 5和2順序不一定

/*
*  執行任務 1,接下來遇到串行隊列異步執行,不會阻塞線程,接下來有可能執行任務 5
*  在異步執行的過程中,先執行任務 2,接下來遇到同步執行,并且將同步任務 3 放到當前串行隊列當中
*  所以任務 3 會等待此串行隊列中的任務 4 執行完畢
*  由于次串行隊列中遇到同步任務 3,他就會等待任務 3 執行完成之后再執行任務 4,形成死鎖
*  造成程序崩潰
*/

場景四

- (void)examine {
    NSLog(@"1"); // 任務1
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2"); // 任務2
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"3"); // 任務3
        });
        NSLog(@"4"); // 任務4
    });
    NSLog(@"5"); // 任務5
}

// log:
1
2
5
3
4
// 5和2順序不定
/*
*  首先,將【任務1、異步線程、任務5】加入Main Queue中,異步線程中的任務是:【任務2、同步線程、任務4】。
*  所以,先執行任務1,然后將異步線程中的任務加入到Global Queue中,因為異步線程,所以任務5不用等待,結果就是2和5的輸出順序不一定。
*  然后再看異步線程中的任務執行順序。任務2執行完以后,遇到同步線程。將同步線程中的任務加入到Main Queue中,這時加入的任務3在任務5的后面。
*  當任務3執行完以后,沒有了阻塞,程序繼續執行任務4。
*  從以上的分析來看,得到的幾個結果:1最先執行;2和5順序不一定;4一定在3后面。
*/

場景五

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"1"); // 任務1
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2"); // 任務2
    });
    NSLog(@"3"); // 任務3
});
NSLog(@"4"); // 任務4
while (1) {
}
NSLog(@"5"); // 任務5

// log:
1
4
// 1和4順序不定
/*
*  和上面幾個案例的分析類似,先來看看都有哪些任務加入了Main Queue:【異步線程、任務4、死循環、任務5】。
*  在加入到Global Queue異步線程中的任務有:【任務1、同步線程、任務3】。
*  第一個就是異步線程,任務4不用等待,所以結果任務1和任務4順序不一定。
*  任務4完成后,程序進入死循環,Main Queue阻塞。但是加入到Global Queue的異步線程不受影響,繼續執行任務1后面的同步線程。
*  同步線程中,將任務2加入到了主線程,并且,任務3等待任務2完成以后才能執行。
*  這時的主線程,已經被死循環阻塞了。所以任務2無法執行,當然任務3也無法執行,在死循環后的任務5也不會執行。
*  最終,只能得到1和4順序不定的結果。
*/

場景六

- (void)examine {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"A");
    });
    NSLog(@"B");
    dispatch_queue_t queueTemp = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ul);
    dispatch_async(queueTemp, ^{
        NSLog(@"C");
    });
    dispatch_async(queueTemp, ^{
        NSLog(@"D");
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"E");
    });
    /*
     *  此方法為異步執行,即使 afterDelay 為 0,仍為異步
     *  method 方法會在主線程中執行
     *  所以,此方法相當于 主線程中的異步任務
     */
    [self performSelector:@selector(method) withObject:nil afterDelay:0.0];
    NSLog(@"F");
}
/*
*  執行順序為 B C D F A E G
*  其中 C 和 D 的順序不定
*  并且 C 和 D 可能在 B 和 G 之間的任一個時刻執行
*/
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容