iOS 線程相關(guān)面試題


問(wèn)題:項(xiàng)目中用到的多線程技術(shù)


使用dispatch_once_t創(chuàng)建單例
使用dispatch_after延遲做事情
使用dispatch_async異步做一些耗時(shí)或者不影響整理流程的操作,比如清除緩存操作,異步網(wǎng)絡(luò)請(qǐng)求


問(wèn)題:六種組合


|----------------------------------------------------------------------- |
|           |   并發(fā)隊(duì)列           |    手動(dòng)創(chuàng)建的串行隊(duì)列   |    主隊(duì)列       |
|----------------------------------------------------------------------- |
|同步(sync)  |   沒(méi)有開(kāi)啟新線程      |     沒(méi)有開(kāi)啟新線程     |   沒(méi)有開(kāi)啟新線程  |
|           |    串行執(zhí)行任務(wù)       |     串行執(zhí)行任務(wù)      |    串行執(zhí)行任務(wù)   |
|----------------------------------------------------------------------- |
|異步(async)|  有開(kāi)啟新線程         |   有開(kāi)啟新線程        | "沒(méi)有開(kāi)啟"新線程  |
|           |   并發(fā)執(zhí)行任務(wù)        |     串行執(zhí)行任務(wù)       |     串行執(zhí)行任務(wù) |
|-----------------------------------------------------------------------

問(wèn)題:NSOperation


NSOperation的作用

配合使用NSOperation和NSOperationQueue也能實(shí)現(xiàn)多線程編程

NSOperation和NSOperationQueue實(shí)現(xiàn)多線程的具體步驟
先將需要執(zhí)行的操作封裝到一個(gè)NSOperation對(duì)象中
然后將NSOperation對(duì)象添加到NSOperationQueue中
系統(tǒng)會(huì)自動(dòng)將NSOperationQueue中的NSOperation取出來(lái)
將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行

NSOperation的子類

使用NSOperation子類的方式有3種
NSInvocationOperation
NSBlockOperation
自定義子類繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的方法

NSInvocationOperation

創(chuàng)建NSInvocationOperation對(duì)象

- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

調(diào)用start方法開(kāi)始執(zhí)行操作

- (void)start;

一旦執(zhí)行操作,就會(huì)調(diào)用target的sel方法

注意
默認(rèn)情況下,調(diào)用了start方法后并不會(huì)開(kāi)一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作
只有將NSOperation放到一個(gè)NSOperationQueue中,才會(huì)異步執(zhí)行操作

- (void)viewDidLoad {
    [super viewDidLoad];
    [self invocationOperation];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)invocationOperation
{
    //以下兩步只是在主線程執(zhí)行run
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    [op start];
}

- (void)run
{
    NSLog(@"------%@", [NSThread currentThread]);
}

@end

NSBlockOperation

創(chuàng)建NSBlockOperation對(duì)象

+ (id)blockOperationWithBlock:(void (^)(void))block;

通過(guò)addExecutionBlock:方法添加更多的操作

- (void)addExecutionBlock:(void (^)(void))block;

調(diào)用start方法開(kāi)始執(zhí)行操作

- (void)start;

eg:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self blockOperation];
}

- (void)blockOperation
{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 在主線程
        NSLog(@"下載1------%@", [NSThread currentThread]);
    }];
    
    // 添加額外的任務(wù)(隨機(jī)在主線程執(zhí)行或者隨機(jī)在子線程執(zhí)行)
    [op addExecutionBlock:^{
        NSLog(@"下載2------%@", [NSThread currentThread]);
    }];

    [op addExecutionBlock:^{
        NSLog(@"下載3------%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"下載4------%@", [NSThread currentThread]);
    }];
    
    [op start];
}

NSOperationQueue

NSOperationQueue的作用
NSOperation可以調(diào)用start方法來(lái)執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的
如果將NSOperation添加到NSOperationQueue(操作隊(duì)列)中,系統(tǒng)會(huì)自動(dòng)異步執(zhí)行NSOperation中的操作

添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
最大并發(fā)數(shù)的相關(guān)方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
取消隊(duì)列的所有操作
- (void)cancelAllOperations;
提示:也可以調(diào)用NSOperation的- (void)cancel方法取消單個(gè)操作
暫停和恢復(fù)隊(duì)列
- (void)setSuspended:(BOOL)b; // YES代表暫停隊(duì)列,NO代表恢復(fù)隊(duì)列。 隊(duì)列暫停時(shí)(當(dāng)前正在執(zhí)行任務(wù)是不會(huì)停止)(將要調(diào)度到線程里任務(wù))
(BOOL)isSuspended;(圖片列表:加載+刷新)
操作優(yōu)先級(jí)

設(shè)置NSOperation在queue中的優(yōu)先級(jí),可以改變操作的執(zhí)行優(yōu)先級(jí)

- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;

優(yōu)先級(jí)的取值
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
操作依賴

NSOperation之間可以設(shè)置依賴來(lái)保證執(zhí)行順序
比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,可以這么寫
[operationB addDependency:operationA]; // 操作B依賴于操作A

可以在不同queue的NSOperation之間創(chuàng)建依賴關(guān)系

操作監(jiān)聽(tīng)

可以監(jiān)聽(tīng)一個(gè)操作的執(zhí)行完畢

  • (void (^)(void))completionBlock;
  • (void)setCompletionBlock:(void (^)(void))block;

自定義NSOperation

自定義NSOperation的步驟很簡(jiǎn)單
重寫- (void)main方法,在里面實(shí)現(xiàn)想執(zhí)行的任務(wù)

重寫- (void)main方法的注意點(diǎn)
自己創(chuàng)建自動(dòng)釋放池(因?yàn)槿绻钱惒讲僮?,無(wú)法訪問(wèn)主線程的自動(dòng)釋放池)
經(jīng)常通過(guò)- (BOOL)isCancelled方法檢測(cè)操作是否被取消,對(duì)取消做出響應(yīng)

針對(duì)上面的自定義NSOperation和NSOperationQueue有如下的demo

首先自定義一個(gè)NSOperation,即新建一個(gè)類繼承于NSOperation

.h

#import <Foundation/Foundation.h>

@interface XMGOperation : NSOperation

@end

.m

#import "XMGOperation.h"

@implementation XMGOperation

/**
 * 需要執(zhí)行的任務(wù)  XMGOperation自定義的方法放在這里面,重寫main
 */
- (void)main
{
    for (NSInteger i = 0; i<1000; i++) {
        //確保當(dāng)前線程取消后能立即生效,視情況而定
        if (self.isCancelled) return;
        NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
    }
    
    //確保當(dāng)前耗時(shí)線程取消后能生效,不執(zhí)行download2,download3
    if (self.isCancelled) return;
    
    for (NSInteger i = 0; i<1000; i++) {
        NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
    }
    
    if (self.isCancelled) return;
    
    for (NSInteger i = 0; i<1000; i++) {
        NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
    }
    if (self.isCancelled) return;
    
}


@end
演示三種創(chuàng)建operation場(chǎng)景,并添加到NSOperationQueue中
- (void)operationQueue1
{
    // 創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 創(chuàng)建操作(任務(wù))
    // 創(chuàng)建NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
    
    // 創(chuàng)建NSBlockOperation  ,這個(gè)依舊子線程
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download3 --- %@", [NSThread currentThread]);
    }];
    
    [op3 addExecutionBlock:^{
        NSLog(@"download4 --- %@", [NSThread currentThread]);
    }];
    [op3 addExecutionBlock:^{
        NSLog(@"download5 --- %@", [NSThread currentThread]);
    }];
    
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download6 --- %@", [NSThread currentThread]);
    }];
    
    // 創(chuàng)建自定義的operation對(duì)象 ,類名為XMGOperation,
    XMGOperation *op5 = [[XMGOperation alloc] init];
    
    // 添加任務(wù)到隊(duì)列中
    [queue addOperation:op1]; // [op1 start] ,添加到隊(duì)列中去,自動(dòng)默認(rèn)start
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
    [queue addOperation:op4]; // [op4 start]
    [queue addOperation:op5]; // [op5 start]
}

- (void)download1
{
    NSLog(@"download1 --- %@", [NSThread currentThread]);
}

- (void)download2
{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
}
演示不用創(chuàng)建Blockoperation場(chǎng)景,直接給NSOperationQueue添加block
- (void)operationQueue2
{
    // 創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 
    
#warning 這兩步可直接簡(jiǎn)化為給NSOperationQueue添加block
    // 創(chuàng)建操作
    //    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    //        NSLog(@"download1 --- %@", [NSThread currentThread]);
    //    }];
    
    // 添加操作到隊(duì)列中
    //    [queue addOperation:op1];
    
    
    //上面兩句作用 等同于 addOperationWithBlock
    
#warning 也就是下面這句 addOperationWithBlock
    [queue addOperationWithBlock:^{
        NSLog(@"download1 --- %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"download2 --- %@", [NSThread currentThread]);
    }];
}
演示NSOperationQueue并發(fā)數(shù),設(shè)置為1直接就成串行了
- (void)opetationQueue3
{
    // 創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 設(shè)置最大并發(fā)操作數(shù)
    //    queue.maxConcurrentOperationCount = 2;
    queue.maxConcurrentOperationCount = 1; // 就變成了子線程的串行隊(duì)列,
    
    // 添加操作
    [queue addOperationWithBlock:^{
        NSLog(@"download1 --- %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"download2 --- %@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"download3 --- %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"download4 --- %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"download5 --- %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"download6 --- %@", [NSThread currentThread]);
    }];
}
演示取消和暫停
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 設(shè)置最大并發(fā)操作數(shù)
    queue.maxConcurrentOperationCount = 1; // 就變成了串行隊(duì)列
    
    // 添加操作
    [queue addOperationWithBlock:^{
//        NSLog(@"download1 --- %@", [NSThread currentThread]);
//        [NSThread sleepForTimeInterval:1.0];
        for (NSInteger i = 0; i<5000; i++) {
            NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
        }
    }];
    [queue addOperationWithBlock:^{
//        NSLog(@"download2 --- %@", [NSThread currentThread]);
        //        [NSThread sleepForTimeInterval:1.0];
        for (NSInteger i = 0; i<1000; i++) {
            NSLog(@"download2 --- %@", [NSThread currentThread]);
        }
    }];
    [queue addOperationWithBlock:^{
//        NSLog(@"download3 --- %@", [NSThread currentThread]);
//        [NSThread sleepForTimeInterval:1.0];
        for (NSInteger i = 0; i<1000; i++) {
            NSLog(@"download3 --- %@", [NSThread currentThread]);
        }
    }];
    
    //添加自定義的Operation,在里面阻斷外界暫停和取消操作
    [queue addOperation:[[XMGOperation alloc] init]];
    
    self.queue = queue;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//演示一 線程的暫停和繼續(xù)
// 如果在線程里做耗時(shí)操作,執(zhí)行到一半,暫停是暫時(shí)不會(huì)執(zhí)行的,直到這個(gè)耗時(shí)線程做完會(huì)暫停
    
//    if (self.queue.isSuspended) {
//        // 恢復(fù)隊(duì)列,繼續(xù)執(zhí)行
//        self.queue.suspended = NO;
//    } else {
//        // 暫停(掛起)隊(duì)列,暫停執(zhí)行
//        self.queue.suspended = YES;
//    }
 
//演示二 線程的取消
    //把線程取消,直接消亡
    [self.queue cancelAllOperations];
    
//eg:NSOperationQueue線程的暫停和取消功能,如果這個(gè)線程正在做一個(gè)耗時(shí)操作(比如遍歷),暫停和取消會(huì)等到這個(gè)線程結(jié)束,才會(huì)結(jié)束queue,所以為了避免這種情況,自定義一個(gè)NSOperation(比如自己定義的XMGOperation),在耗時(shí)操作里監(jiān)聽(tīng)取消和暫停狀態(tài),就是一個(gè)很好地將解決辦法
}

演示線程依賴
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download1----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download2----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download3----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i<10; i++) {
            NSLog(@"download4----%@", [NSThread  currentThread]);
        }
    }];
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download5----%@", [NSThread  currentThread]);
    }];
    //op5線程完成以后調(diào)用completionBlock
    op5.completionBlock = ^{
        NSLog(@"op5執(zhí)行完畢---%@", [NSThread currentThread]);
    };
    
    // 設(shè)置依賴 (再添加之前設(shè)置依賴)
    [op3 addDependency:op1];
    [op3 addDependency:op2];
    [op3 addDependency:op4];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    [queue addOperation:op5];
}
線程通信

NSThread線程通信

    // 回到主線程,顯示圖片
    //waitUntilDone為YES意味著當(dāng)前方法會(huì)卡主,直到完成才會(huì)往下走
    [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];

    //    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
    
    //    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];

NSOperation的線程通信

    // 回到主線程
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        self.imageView.image = image;
    }];

GCD的線程通信

    // 回到主線程
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageView.image = image;
    });

GCD的一些函數(shù)


GCD的簡(jiǎn)單使用
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self syncConcurrent];
}

/**
 * 同步函數(shù) + 主隊(duì)列:(任務(wù)會(huì)卡住)
 */
- (void)syncMain
{
    NSLog(@"syncMain ----- begin");
    
    // 1.獲得主隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.將任務(wù)加入隊(duì)列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
    
    NSLog(@"syncMain ----- end");
}

/**
 * 異步函數(shù) + 主隊(duì)列:只在主線程中執(zhí)行任務(wù) (這個(gè)特例說(shuō)明了異步函數(shù)可以開(kāi)啟新線程,但并不是絕對(duì)可以開(kāi)啟新線程)
 */
- (void)asyncMain
{
    // 1.獲得主隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.將任務(wù)加入隊(duì)列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
}

/**
 * 同步函數(shù) + 串行隊(duì)列:不會(huì)開(kāi)啟新的線程,在當(dāng)前線程執(zhí)行任務(wù)。任務(wù)是串行的,執(zhí) 任務(wù),再執(zhí)行下一個(gè)任務(wù)
 */
- (void)syncSerial
{
    // 1.創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL);
    
    // 2.將任務(wù)加入隊(duì)列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
}

/**
 * 異步函數(shù) + 串行隊(duì)列:會(huì)開(kāi)啟新的線程,但是任務(wù)是串行的,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)
 */
- (void)asyncSerial
{
    // 1.創(chuàng)建串行隊(duì)列(串行隊(duì)列只能創(chuàng)建)
    // DISPATCH_QUEUE_SERIAL 和 NULL 都會(huì)是串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL);
    //  dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);
    
    // 2.將任務(wù)加入隊(duì)列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
}

/**
 * 同步函數(shù) + 并發(fā)隊(duì)列:不會(huì)開(kāi)啟新的線程(在主線程做)
 */
- (void)syncConcurrent
{
    // 1.獲得全局的并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2.將任務(wù)加入隊(duì)列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
    NSLog(@"syncConcurrent--------end");
}

/**
 * 異步函數(shù) + 并發(fā)隊(duì)列:可以同時(shí)開(kāi)啟多條線程
 */
- (void)asyncConcurrent
{
    // 1.0創(chuàng)建一個(gè)并發(fā)隊(duì)列
    // label : 相當(dāng)于隊(duì)列的名字  "com.520it.queue"
    // DISPATCH_QUEUE_CONCURRENT 隊(duì)列類型,并發(fā)隊(duì)列
    // dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 1.1直接獲得全局的并發(fā)隊(duì)列
    //DISPATCH_QUEUE_PRIORITY_DEFAULT優(yōu)先級(jí),作用:優(yōu)先執(zhí)行順序
    //默認(rèn)傳0
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2.將任務(wù)加入隊(duì)列
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<10; i++) {
            NSLog(@"1-----%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<10; i++) {
            NSLog(@"2-----%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<10; i++) {
            NSLog(@"3-----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncConcurrent--------end");
//    dispatch_release(queue);  現(xiàn)在GCD的create現(xiàn)在不需要釋放了
}

@end
隊(duì)列組

在使用GCD進(jìn)行任務(wù)操作時(shí),有時(shí)會(huì)希望若干個(gè)任務(wù)執(zhí)行之間有先后執(zhí)行的依賴關(guān)系。

eg:

// 創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
// 創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);

// 添加異步任務(wù)
dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 5; i++) {
        NSLog(@"任務(wù)1-%@", [NSThread currentThread]);
    }
});

dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 5; i++) {
        NSLog(@"任務(wù)2-%@", [NSThread currentThread]);
    }
});

//等前面的任務(wù)執(zhí)行完畢后,會(huì)自動(dòng)執(zhí)行這個(gè)任務(wù)
dispatch_group_notify(group, queue, ^{
    dispatch_async(dispatch_get_main_queue(), ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
        }
    });
});
/*下面這是大牛講解時(shí)
//等前面的任務(wù)執(zhí)行完畢后,會(huì)自動(dòng)執(zhí)行這個(gè)任務(wù)
dispatch_group_notify(group, dispatch_get_main_queue, ^{
    dispatch_async(dispatch_get_main_queue(), ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
        }
    });
});
*/
dispatch_set_target_queue

用法一:指定優(yōu)先級(jí)
?

用法二:控制隊(duì)列執(zhí)行階層
如果在多個(gè)SERIAL Dispatch Queue中用dispatch_set_target_queue函數(shù)制定目標(biāo)為某一個(gè)SERIAL Dispatch Queue,那么原本應(yīng)并行執(zhí)行的多個(gè)SERIAL Dispatch Queue,在目標(biāo)SERIAL Dispatch Queue上只能同時(shí)執(zhí)行一個(gè)處理。如下:

//1.創(chuàng)建目標(biāo)隊(duì)列
dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);

//2.創(chuàng)建3個(gè)串行隊(duì)列
dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);

//3.將3個(gè)串行隊(duì)列分別添加到目標(biāo)隊(duì)列
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_set_target_queue(queue3, targetQueue);


dispatch_async(queue1, ^{
    NSLog(@"1 in");
    [NSThread sleepForTimeInterval:3.f];
    NSLog(@"1 out");
});

dispatch_async(queue2, ^{
    NSLog(@"2 in");
    [NSThread sleepForTimeInterval:2.f];
    NSLog(@"2 out");
});

dispatch_async(queue3, ^{
    NSLog(@"3 in");
    [NSThread sleepForTimeInterval:1.f];
    NSLog(@"3 out");
});

打印結(jié)果:
2019-05-06 15:52:36.025069+0800 Test[4273:498926] 1 in
2019-05-06 15:52:39.029506+0800 Test[4273:498926] 1 out
2019-05-06 15:52:39.029782+0800 Test[4273:498926] 2 in
2019-05-06 15:52:41.034457+0800 Test[4273:498926] 2 out
2019-05-06 15:52:41.034623+0800 Test[4273:498926] 3 in
2019-05-06 15:52:42.037019+0800 Test[4273:498926] 3 out

//如上,如果不設(shè)置targetQueue目標(biāo)隊(duì)列,那么queue1queue2queue3亂序跑。
dispatch_after

下面代碼3秒之后執(zhí)行,可以是任何線程

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);

dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"waited at east three seconds.");
});
dispatch_group_notify
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(@"1");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"2");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"3");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"done");
});
dispatch_group_wait
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(@"1");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"2");
    sleep(1.0);
});
dispatch_group_async(group, queue, ^{
    NSLog(@"3");
});

//DISPATCH_TIME_FOREVER類型也是dispatch_time_t型
//注意點(diǎn),一旦調(diào)用dispatch_group_wait函數(shù),該函數(shù)就處于調(diào)用狀態(tài)而不返回。即是當(dāng)前線程已經(jīng)卡在這兒,不向后執(zhí)行,必須等待前面任務(wù)處理有結(jié)果或者自己設(shè)定的dispatch_time_t時(shí)間結(jié)束。
BOOL result =  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result == 0) {//返回值為0,那么全部處理執(zhí)行結(jié)束
    NSLog(@"done");
}else{//如果時(shí)間過(guò),但是前面的任務(wù)還沒(méi)有跑完,會(huì)走到這兒
    NSLog(@"任務(wù)仍在執(zhí)行");
};
dispatch_barrier_async
//柵欄的隊(duì)列不能使全局的并發(fā),要是手動(dòng)創(chuàng)建的并發(fā)  (待考證)
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----3-----%@", [NSThread currentThread]);
});

//barrier柵欄效果
//官方解釋:前面的執(zhí)行完才到這,這個(gè)執(zhí)行完才執(zhí)行后面(★待考證)
dispatch_barrier_async(queue, ^{
    NSLog(@"----barrier-----%@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"----4-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----5-----%@", [NSThread currentThread]);
});
dispatch_apply

該函數(shù)按指定的次數(shù)將指定的block追加到指定的Dispatch Queue中,并等待全部處理結(jié)束。推薦在dispatch_async中非同步的執(zhí)行dispatch_apply函數(shù)。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"----------------主線程開(kāi)始------------");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async( queue, ^{
        
        NSLog(@"子線程%@",[NSThread currentThread]);
        
        dispatch_apply(3, queue, ^(size_t index) {
            
            NSLog(@"追加第%zu次任務(wù)",index);
        });
        
        NSLog(@"done");
        
        //可以回到主線程進(jìn)行通信了
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"刷新界面");
        });
    });
    
    NSLog(@"----------------主線程繼續(xù)------------");
}

打印結(jié)果:
2019-05-07 10:16:06.001245+0800 Test[2820:134508] ----------------主線程開(kāi)始------------
2019-05-07 10:16:06.001436+0800 Test[2820:134508] ----------------主線程繼續(xù)------------
2019-05-07 10:16:06.001476+0800 Test[2820:134543] 子線程<NSThread: 0x600000c20a00>{number = 3, name = (null)}
2019-05-07 10:16:06.001567+0800 Test[2820:134543] 追加第0次任務(wù)
2019-05-07 10:16:06.001637+0800 Test[2820:134543] 追加第1次任務(wù)
2019-05-07 10:16:06.001700+0800 Test[2820:134543] 追加第2次任務(wù)
2019-05-07 10:16:06.001761+0800 Test[2820:134543] done
2019-05-07 10:16:06.009918+0800 Test[2820:134508] 刷新界面
dispatch_suspend和dispatch_resume

線程的掛起和恢復(fù)。dispatch_suspend并不會(huì)立即暫停正在運(yùn)行的block,而是在當(dāng)前block執(zhí)行完成后,暫停后續(xù)的block執(zhí)行。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
    //提交第一個(gè)block,延時(shí)2秒打印。
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"After 2 seconds...");
    });
    //提交第二個(gè)block,也是延時(shí)2秒打印
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"After 2 seconds again...");
    });
    //延時(shí)一秒
    NSLog(@"sleep 1 second...");
    sleep(1);
    //掛起隊(duì)列
    NSLog(@"suspend...");
    dispatch_suspend(queue);
    //延時(shí)10秒
    NSLog(@"sleep 10 second...");
    sleep(10);
    //恢復(fù)隊(duì)列
    NSLog(@"resume...");
    dispatch_resume(queue);
}

問(wèn)題:GCD和nsopreation什么場(chǎng)景使用哪個(gè)好點(diǎn)


GCD主要特點(diǎn):

1)GCG 是iOS4.0推出的,主要針對(duì)多核CPU 做了優(yōu)化
2)GCD是 C 語(yǔ)言的技術(shù)
3)GCD 提供了一些 NSOperation 不具備的功能,比如一次性執(zhí)行(創(chuàng)建單例),延遲執(zhí)行,調(diào)度組.

NSOperation 特點(diǎn):

  1. NSOperation 是 iOS2.0后推出的,iOS4.0之后重寫了NSOperation.
  2. NSOperation 將操作(異步的任務(wù))添加到隊(duì)列(并發(fā)隊(duì)列),就會(huì)執(zhí)行制定操作的函數(shù).
  3. NSOperation里可以方便的設(shè)置操作:
    1??最大并發(fā)數(shù)
    2??隊(duì)列的暫停/繼續(xù)
    3??取消所有的操作
    4??指定操作之間的依賴關(guān)系(GCD可以用同步實(shí)現(xiàn))
    **使用NSOperation 需要注意幾點(diǎn)點(diǎn):
  4. 注意避免產(chǎn)生循環(huán)依賴
  5. 要先設(shè)置依賴關(guān)系,然后添加到隊(duì)列

GCD 和 NSOperation的區(qū)別主要表現(xiàn)在以下幾方面:

  1. GCD是一套 C 語(yǔ)言API,執(zhí)行和操作簡(jiǎn)單高效,因此NSOperation底層也通過(guò)GCD實(shí)現(xiàn),這是他們之間最本質(zhì)的區(qū)別.因此如果希望自定義任務(wù),建議使用NSOperation;

  2. 依賴關(guān)系,NSOperation可以設(shè)置操作之間的依賴(可以跨隊(duì)列設(shè)置),GCD無(wú)法設(shè)置依賴關(guān)系,不過(guò)可以通過(guò)同步來(lái)實(shí)現(xiàn)這種效果;

  3. KVO(鍵值對(duì)觀察),NSOperation容易判斷操作當(dāng)前的狀態(tài)(是否執(zhí)行,是否取消等),對(duì)此GCD無(wú)法通過(guò)KVO進(jìn)行判斷;
    (指的應(yīng)該是:NSOperationQueue中使用了KVO,當(dāng)NSOperation對(duì)象狀態(tài)變化(finished,canceled等)時(shí),觀察者可以知道變化,從而做出相應(yīng)的處理(移除已完成的等等))

  4. 優(yōu)先級(jí),NSOperation可以設(shè)置自身的優(yōu)先級(jí),但是優(yōu)先級(jí)高的不一定先執(zhí)行,GCD只能設(shè)置隊(duì)列的優(yōu)先級(jí),如果要區(qū)分block任務(wù)的優(yōu)先級(jí),需要很復(fù)雜的代碼才能實(shí)現(xiàn);

  5. 繼承,NSOperation是一個(gè)抽象類.實(shí)際開(kāi)發(fā)中常用的是它的兩個(gè)子類:NSInvocationOperation和NSBlockOperation,同樣我們可以自定義NSOperation,GCD執(zhí)行任務(wù)可以自由組裝,沒(méi)有繼承那么高的代碼復(fù)用度;

  6. 效率,直接使用GCD效率確實(shí)會(huì)更高效,NSOperation會(huì)多一點(diǎn)開(kāi)銷,但是通過(guò)NSOperation可以獲得依賴,優(yōu)先級(jí),繼承,鍵值對(duì)觀察這些優(yōu)勢(shì),相對(duì)于多的那么一點(diǎn)開(kāi)銷確實(shí)很劃算,魚和熊掌不可得兼,取舍在于開(kāi)發(fā)者自己;

7)可以隨時(shí)取消準(zhǔn)備執(zhí)行的任務(wù)(已經(jīng)在執(zhí)行的不能取消),GCD沒(méi)法停止已經(jīng)加入queue 的 block(雖然也能實(shí)現(xiàn),但是需要很復(fù)雜的代碼)

基于GCD簡(jiǎn)單高效,更強(qiáng)的執(zhí)行能力,操作不太復(fù)雜的時(shí)候,優(yōu)先選用GCD;而比較復(fù)雜的任務(wù)可以自己通過(guò)NSOperation實(shí)現(xiàn).


問(wèn)題:atomic


atomic用于保證屬性setter、getter的原子性操作,相當(dāng)于在setter和getter內(nèi)部加了線程同步的鎖,從而保證了setter和getter內(nèi)部是線程同步的。

可以參考源碼objc4的objc-accessors.mm
首先看設(shè)置屬性

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

點(diǎn)擊查看reallySetProperty方法

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else { //如果是atomic,可以看到加了一個(gè)spinlock_t自旋鎖
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;
        slotlock.unlock();
    }

    objc_release(oldValue);
}

再看get屬性

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot]; //如果是atomic,可以看到加了一個(gè)spinlock_t自旋鎖
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

它并不能保證"使用屬性"的過(guò)程是線程安全的。只是保證了set和get方法內(nèi)部是線程安全的,但是并不能保證屬性的過(guò)程是線程安全的。

說(shuō)atomic并不能保證使用屬性的過(guò)程是線程安全的,

舉個(gè)例子
1.對(duì)于NSArray類型 @property(atomic)NSArray *array我們用atomic修飾,數(shù)組的添加和刪除并不是線程安全的,這是因?yàn)閿?shù)組比較特殊,我們要分成兩部分考慮,一部分是&array也就是這個(gè)數(shù)組本身,另一部分是他所指向的內(nèi)存部分。atomic限制的只是&array部分,對(duì)于它指向的對(duì)象沒(méi)有任何限制。
eg:

MJPerson *p = [[MJPerson alloc] init];//初始化類,類里面之前生命了一個(gè)可變數(shù)組dataArray屬性
p.dataArray = [NSMutableArray array];//初始化dataArray,這里面p.dataArray是線程安全的,因?yàn)檎{(diào)用了set方法
[p.dataArray addObject:@"1"];  //這里面p.dataArray是線程安全的,因?yàn)檎{(diào)用了get方法,但是addObject卻沒(méi)有安全管控
[p.dataArray addObject:@"2"];

2、比如如果線程 A 調(diào)了 getter,與此同時(shí)線程 B 、線程 C 都調(diào)了 setter——那最后線程 A get 到的值,3種都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同時(shí),最終這個(gè)屬性的值,可能是 B set 的值,也有可能是 C set 的值。

如下

@property (atomic, assign) NSInteger intA;

- (void)viewDidLoad {
   [super viewDidLoad];
    
   //開(kāi)啟一個(gè)線程對(duì)intA的值+1
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       for (int i = 0;i < 1000;i ++){
           self.intA = self.intA + 1;
       }
       NSLog(@"首先加一:intA : %ld",(long)self.intA);
   });
   
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"首先加一后:intA : %ld",(long)self.intA);
    });
    
   //開(kāi)啟一個(gè)線程對(duì)intA的值+1
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       for (int i = 0;i < 1000;i ++){
           self.intA = self.intA + 1;
       }
       NSLog(@"其次加一:intA : %ld",(long)self.intA);
   });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       NSLog(@"其次加一后:intA : %ld",(long)self.intA);
    });
}
@end
打?。?因?yàn)槎际钱惒?,打印順序并不固?
2021-02-03 17:39:09.929929+0800 OC_Test[6945:206188] 首先加一:intA : 1000
2021-02-03 17:39:09.930341+0800 OC_Test[6945:206184] 其次加一:intA : 2000
2021-02-03 17:39:09.930385+0800 OC_Test[6945:206186] 其次加一后:intA : 2000
2021-02-03 17:39:09.931724+0800 OC_Test[6945:206189] 首先加一后:intA : 0

atomic:原子性
nonatomic:非原子性

聲明屬性時(shí)不寫的話默認(rèn)就是atomic

忙等

自旋鎖(Spinlock)是一種忙等待鎖,線程反復(fù)檢查鎖變量是否可用,不會(huì)掛起,避免了進(jìn)程上下文的調(diào)度開(kāi)銷,適合阻塞很短時(shí)間的場(chǎng)合。當(dāng)然也就不適合單CPU單線程上使用。


問(wèn)題:dispatch_sync


意味著將指定的block“同步”追加到指定的Dispatch Queue中,在追加block結(jié)束之前,dispatch_sync函數(shù)會(huì)一直等待。可以認(rèn)為是簡(jiǎn)單版的dispatch_group_wait

由于sync特性,所以使用sync函數(shù)往當(dāng)前"串行"隊(duì)列中添加任務(wù),會(huì)卡住當(dāng)前的串行隊(duì)列(產(chǎn)生死鎖)

死鎖的原因是:隊(duì)列引起的循環(huán)等待。由于當(dāng)前串行隊(duì)列執(zhí)行到dispatch_sync處等待dispatch_sync的返回,dispatch_sync必須等待block執(zhí)行完畢后才能返回,由于當(dāng)前隊(duì)列是串行隊(duì)列,先進(jìn)先出,但是我們通過(guò)dispatch_sync新放入的block位于隊(duì)列后面,現(xiàn)在得不到執(zhí)行,所以鎖住了。

問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?會(huì)!
NSLog(@"執(zhí)行任務(wù)1");

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
    NSLog(@"執(zhí)行任務(wù)2");
});

NSLog(@"執(zhí)行任務(wù)3");

原因:
因?yàn)榘淹降娜蝿?wù)2添加到主隊(duì)列,dispatch_sync要求立馬在當(dāng)前線程同步執(zhí)行任務(wù),走到這一步想要立即執(zhí)行。想要執(zhí)行隊(duì)列里的任務(wù)2,必須要等前面一整個(gè)任務(wù)執(zhí)行完。任務(wù)3在等任務(wù)2,任務(wù)2在等任務(wù)3。
用圖表示為

當(dāng)前線程      |         主隊(duì)列
任務(wù)1        |      viewDidLoad
sync        |       任務(wù)2
任務(wù)3        |

那么把上述的dispatch_sync換成dispatch_async會(huì)不會(huì)產(chǎn)生死鎖?不會(huì)!
NSLog(@"執(zhí)行任務(wù)1");

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
    NSLog(@"執(zhí)行任務(wù)2");
});

NSLog(@"執(zhí)行任務(wù)3");

原因:
dispatch_async不要求立馬在當(dāng)前線程同步執(zhí)行任務(wù)
//打印結(jié)果為 執(zhí)行任務(wù)1 執(zhí)行任務(wù)3 執(zhí)行任務(wù)2

問(wèn)題:以下代碼會(huì)不會(huì)產(chǎn)生死鎖?不會(huì)!
  • (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"執(zhí)行任務(wù)1");

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    //將上面放在主線程會(huì)死鎖的代碼放到子線程里不會(huì)死鎖
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
    NSLog(@"執(zhí)行任務(wù)2");
    });
    });

    NSLog(@"執(zhí)行任務(wù)3");
    }

問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?會(huì)!
NSLog(@"執(zhí)行任務(wù)1");

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{ // 0
    NSLog(@"執(zhí)行任務(wù)2");
    
    dispatch_sync(queue, ^{ // 1
        NSLog(@"執(zhí)行任務(wù)3");
    });
    
    NSLog(@"執(zhí)行任務(wù)4");
});

NSLog(@"執(zhí)行任務(wù)5");
問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?會(huì)!
NSLog(@"執(zhí)行任務(wù)1");

dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
    NSLog(@"執(zhí)行任務(wù)2");
    
    dispatch_sync(queue, ^{ // 1
        NSLog(@"執(zhí)行任務(wù)3");
    });
    
    NSLog(@"執(zhí)行任務(wù)4");
});

NSLog(@"執(zhí)行任務(wù)5");
問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?不會(huì)!雖是同步,但確實(shí)并發(fā)隊(duì)列
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1");
    
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    
    NSLog(@"5");
    // Do any additional setup after loading the view.
}
下面代碼執(zhí)行會(huì)打印什么
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

dispatch_async(queue, ^{
    NSLog(@"1");
    [self performSelector:@selector(test) withObject:nil afterDelay:.0];
    NSLog(@"3");
    
    //添加這行代碼,啟動(dòng)RunLoop程序才能打印2
    //[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

});
}
- (void)test
{
    NSLog(@"2");
}

答:會(huì)打印 1 3,但是2不會(huì)打印。因?yàn)閜erformSelector:withObject:afterDelay:這句代碼的本質(zhì)是往Runloop中添加定時(shí)器,而子線程的Runloop在這里沒(méi)有啟動(dòng)。
注意:- (id)performSelector:(SEL)aSelector withObject:(id)object;等不需要Runloop。

下面代碼執(zhí)行會(huì)打印什么
- (void)test
{
    NSLog(@"2");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");
        
        //啟動(dòng)RunLoop,保住線程。然后程序正常運(yùn)行,打印了2
        //[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        //[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];
    
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

答:會(huì)打印 1 ,然后程序崩潰。因?yàn)閳?zhí)行完1,block結(jié)束后線程結(jié)束。


問(wèn)題:信號(hào)量


dispatch_semaphore

semaphore叫做”信號(hào)量”
信號(hào)量的初始值,可以用來(lái)控制線程并發(fā)訪問(wèn)的最大數(shù)量
信號(hào)量的初始值為1,代表同時(shí)只允許1條線程訪問(wèn)資源,保證線程同步

//定義一個(gè)控制最大量的值
int value= 1;
//初始化信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
//執(zhí)行到這句話時(shí),如果信號(hào)量的值<=0,當(dāng)前線程會(huì)進(jìn)入睡眠等待(直到信號(hào)量的值>0);如果信號(hào)量的值>0,就減1,繼續(xù)往下執(zhí)行。
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
//讓信號(hào)量的值加1
dispatch_semaphore_signal(semaphore);

信號(hào)量的使用場(chǎng)景

加鎖
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 10000; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        //臨界區(qū),即待加鎖的代碼區(qū)域

        dispatch_semaphore_signal(semaphore);
    });
}

在進(jìn)行多線程任務(wù)之前,首先創(chuàng)建一個(gè)計(jì)數(shù)為1的信號(hào)量,這樣可以保證同一時(shí)刻只有一個(gè)線程在訪問(wèn)臨界區(qū)。上面代碼,系統(tǒng)會(huì)同時(shí)開(kāi)啟很多線程想要做遍歷里面的事,但是同時(shí)只有一個(gè)線程能去做臨界區(qū)的事情。

異步任務(wù),同步返回
- (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) {
        //task賦值,代碼有點(diǎn)長(zhǎng),就不貼了
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

上面是 AFNetworking 的一段代碼,我且稱之為異步去做,同步返回。這段代碼的功能是通過(guò)異步的請(qǐng)求取得鍵路徑為 keyPath 的任務(wù)數(shù)組 tasks,然后同步返回它。

控制線程并發(fā)數(shù)

在 GCD 中,dispatch_async() 異步操作可以產(chǎn)生新的線程,但是方法本身沒(méi)辦法限制線程的最大并發(fā)數(shù),線程的創(chuàng)建和銷毀是由 GCD 底層管理的。
了解 NSOperationQueue 的同學(xué)肯定知道,通過(guò) maxConcurrentOperationCount 屬性可以設(shè)置它的最大并發(fā)數(shù)。那么在GCD中,對(duì)應(yīng)的解決方法就是使用信號(hào)量。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
for (int i = 0; i < 1000; ++i) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
        //多線程代碼
        
        dispatch_semaphore_signal(semaphore);
    });
}

其實(shí)跟加鎖代碼非常相似,區(qū)別在于,在初始化信號(hào)量時(shí),將計(jì)數(shù)賦值為最大并發(fā)數(shù)。在應(yīng)用場(chǎng)景上,限制線程并發(fā)數(shù)是為了性能考慮,而加鎖是為了安全而考慮。

信號(hào)量和柵欄函數(shù)是鎖嗎

信號(hào)量是屬于很多種加鎖方式的一種,柵欄函數(shù)能實(shí)現(xiàn)讀寫鎖的功能,所以應(yīng)該都屬于鎖。


問(wèn)題:線程池


線程池是一種線程使用模式。 線程過(guò)多會(huì)帶來(lái)調(diào)度開(kāi)銷,進(jìn)而影響緩存局部性和整體性能。 而線程池維護(hù)著多個(gè)線程,等待著監(jiān)督管理者分配可并發(fā)執(zhí)行的任務(wù)。 這避免了在處理短時(shí)間任務(wù)時(shí)創(chuàng)建與銷毀線程的代價(jià)。

線程池的執(zhí)行流程如圖下:
1612430117402.jpg

若線程池大小小于核心線程池大小時(shí)
創(chuàng)建線程執(zhí)行任務(wù)

若線程池大小大于等于核心線程池大小時(shí)
1.先判斷線程池工作隊(duì)列是否已滿
2.若沒(méi)滿就將任務(wù)push進(jìn)隊(duì)列
3.若已滿時(shí),且maximumPoolSize>corePoolSize,將創(chuàng)建新的線程來(lái)4.執(zhí)行任務(wù)
反之則交給飽和策略去處理

參數(shù)名代表意義

corePoolSize
線程池的基本大小(核心線程池大?。?/p>

maximumPool
線程池的最大大小

keepAliveTime
線程池中超過(guò)corePoolSize樹木的空閑線程的最大存活時(shí)間

unit
keepAliveTime參數(shù)的時(shí)間單位

workQueue
任務(wù)阻塞隊(duì)列

threadFactory
新建線程的工廠

handler
當(dāng)提交的任務(wù)數(shù)超過(guò)maxmumPoolSize與workQueue之和時(shí),任務(wù)會(huì)交給RejectedExecutionHandler來(lái)處理

上圖的解釋

我們知道,受限于硬件、內(nèi)存和性能,我們不可能無(wú)限制的創(chuàng)建任意數(shù)量的線程,因?yàn)槊恳慌_(tái)機(jī)器允許的最大線程是一個(gè)有界值。也就是說(shuō)ThreadPoolExecutor管理的線程數(shù)量是有界的。線程池就是用這些有限個(gè)數(shù)的線程,去執(zhí)行提交的任務(wù)。然而對(duì)于多用戶、高并發(fā)的應(yīng)用來(lái)說(shuō),提交的任務(wù)數(shù)量非常巨大,一定會(huì)比允許的最大線程數(shù)多很多。為了解決這個(gè)問(wèn)題,必須要引入排隊(duì)機(jī)制,或者是在內(nèi)存中,或者是在硬盤等容量很大的存儲(chǔ)介質(zhì)中。J.U.C提供的ThreadPoolExecutor只支持任務(wù)在內(nèi)存中排隊(duì),通過(guò)BlockingQueue暫存還沒(méi)有來(lái)得及執(zhí)行的任務(wù)。

任務(wù)的管理是一件比較容易的事,復(fù)雜的是線程的管理,這會(huì)涉及線程數(shù)量、等待/喚醒、同步/鎖、線程創(chuàng)建和死亡等問(wèn)題。ThreadPoolExecutor與線程相關(guān)的幾個(gè)成員變量是:keepAliveTime、allowCoreThreadTimeOut、poolSize、corePoolSize、maximumPoolSize,它們共同負(fù)責(zé)線程的創(chuàng)建和銷毀。
corePoolSize:
線程池的基本大小,即在沒(méi)有任務(wù)需要執(zhí)行的時(shí)候線程池的大小,并且只有在工作隊(duì)列滿了的情況下才會(huì)創(chuàng)建超出這個(gè)數(shù)量的線程。這里需要注意的是:在剛剛創(chuàng)建ThreadPoolExecutor的時(shí)候,線程并不會(huì)立即啟動(dòng),而是要等到有任務(wù)提交時(shí)才會(huì)啟動(dòng),除非調(diào)用了prestartCoreThread/prestartAllCoreThreads事先啟動(dòng)核心線程。再考慮到keepAliveTime和allowCoreThreadTimeOut超時(shí)參數(shù)的影響,所以沒(méi)有任務(wù)需要執(zhí)行的時(shí)候,線程池的大小不一定是corePoolSize。

maximumPoolSize:
線程池中允許的最大線程數(shù),線程池中的當(dāng)前線程數(shù)目不會(huì)超過(guò)該值。如果隊(duì)列中任務(wù)已滿,并且當(dāng)前線程個(gè)數(shù)小于maximumPoolSize,那么會(huì)創(chuàng)建新的線程來(lái)執(zhí)行任務(wù)。這里值得一提的是largestPoolSize,該變量記錄了線程池在整個(gè)生命周期中曾經(jīng)出現(xiàn)的最大線程個(gè)數(shù)。為什么說(shuō)是曾經(jīng)呢?因?yàn)榫€程池創(chuàng)建之后,可以調(diào)用setMaximumPoolSize()改變運(yùn)行的最大線程的數(shù)目。

poolSize:
線程池中當(dāng)前線程的數(shù)量,當(dāng)該值為0的時(shí)候,意味著沒(méi)有任何線程,線程池會(huì)終止;同一時(shí)刻,poolSize不會(huì)超過(guò)maximumPoolSize。

https://www.cnblogs.com/frankyou/p/10135212.html


問(wèn)題:異步執(zhí)行ABC之后在執(zhí)行D的正確理解


首先異步執(zhí)行ABC分兩種情況,一種任務(wù)是同步的,一種任務(wù)是網(wǎng)絡(luò)請(qǐng)求,發(fā)送操作是同步的,但是請(qǐng)求到的結(jié)果是異步的。

任務(wù)是同步時(shí)

第一種GCD group
dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任務(wù)A");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任務(wù)B");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任務(wù)C");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任務(wù)完成執(zhí)行");
    });
第二種 dispatch_barrier_async
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)A");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)B");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)C");
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"阻塞自定義并發(fā)隊(duì)列");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)D");
    });

    dispatch_async(queue, ^{
        NSLog(@"任務(wù)E");
     });
第三種 NSOperation
NSBlockOperation *operatioon1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)A");
    }];
    
    NSBlockOperation *operatioon2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)B");
    }];
    
    NSBlockOperation *operatioon3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)C");
    }];
    
    NSBlockOperation *operatioon4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)D");
    }];
    
    [operatioon4 addDependency:operatioon1];
    [operatioon4 addDependency:operatioon2];
    [operatioon4 addDependency:operatioon3];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operatioon1,operatioon2,operatioon3,operatioon4] waitUntilFinished:YES];
    NSLog(@"完成之后的操作");

任務(wù)時(shí)異步時(shí)(比如網(wǎng)絡(luò)請(qǐng)求異步任務(wù))

第一種 dispatch_group + semaphore
dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任務(wù)A");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"網(wǎng)絡(luò)異步任務(wù)一");
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"同步任務(wù)B");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"網(wǎng)絡(luò)異步任務(wù)二");
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任務(wù)C");
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"同步任務(wù)D");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"網(wǎng)絡(luò)異步任務(wù)四");
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任務(wù)完成執(zhí)行");
    });

第二種 dispatch_group_enter 和 dispatch_group_leave
dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任務(wù)A");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"網(wǎng)絡(luò)異步任務(wù)一");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"同步任務(wù)B");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"網(wǎng)絡(luò)異步任務(wù)二");
            dispatch_group_leave(group);
        });
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"同步任務(wù)C");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"同步任務(wù)D");
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"網(wǎng)絡(luò)異步任務(wù)四");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任務(wù)完成執(zhí)行");
    });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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