iOS 线程相关面试题

作者: 陈盼同学 | 来源:发表于2021-02-03 18:13 被阅读0次

    问题:项目中用到的多线程技术


    使用dispatch_once_t创建单例
    使用dispatch_after延迟做事情
    使用dispatch_async异步做一些耗时或者不影响整理流程的操作,比如清除缓存操作,异步网络请求


    问题:六种组合


    |----------------------------------------------------------------------- |
    |           |   并发队列           |    手动创建的串行队列   |    主队列       |
    |----------------------------------------------------------------------- |
    |同步(sync)  |   没有开启新线程      |     没有开启新线程     |   没有开启新线程  |
    |           |    串行执行任务       |     串行执行任务      |    串行执行任务   |
    |----------------------------------------------------------------------- |
    |异步(async)|  有开启新线程         |   有开启新线程        | "没有开启"新线程  |
    |           |   并发执行任务        |     串行执行任务       |     串行执行任务 |
    |-----------------------------------------------------------------------
    

    问题:NSOperation


    NSOperation的作用

    配合使用NSOperation和NSOperationQueue也能实现多线程编程

    NSOperation和NSOperationQueue实现多线程的具体步骤
    先将需要执行的操作封装到一个NSOperation对象中
    然后将NSOperation对象添加到NSOperationQueue中
    系统会自动将NSOperationQueue中的NSOperation取出来
    将取出的NSOperation封装的操作放到一条新线程中执行

    NSOperation的子类

    使用NSOperation子类的方式有3种
    NSInvocationOperation
    NSBlockOperation
    自定义子类继承NSOperation,实现内部相应的方法

    NSInvocationOperation

    创建NSInvocationOperation对象

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

    调用start方法开始执行操作

    - (void)start;
    

    一旦执行操作,就会调用target的sel方法

    注意
    默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
    只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作

    - (void)viewDidLoad {
        [super viewDidLoad];
        [self invocationOperation];
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (void)invocationOperation
    {
        //以下两步只是在主线程执行run
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
        [op start];
    }
    
    - (void)run
    {
        NSLog(@"------%@", [NSThread currentThread]);
    }
    
    @end
    

    NSBlockOperation

    创建NSBlockOperation对象

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

    通过addExecutionBlock:方法添加更多的操作

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

    调用start方法开始执行操作

    - (void)start;
    

    eg:

    - (void)viewDidLoad {
        [super viewDidLoad];
        [self blockOperation];
    }
    
    - (void)blockOperation
    {
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            // 在主线程
            NSLog(@"下载1------%@", [NSThread currentThread]);
        }];
        
        // 添加额外的任务(随机在主线程执行或者随机在子线程执行)
        [op addExecutionBlock:^{
            NSLog(@"下载2------%@", [NSThread currentThread]);
        }];
    
        [op addExecutionBlock:^{
            NSLog(@"下载3------%@", [NSThread currentThread]);
        }];
        [op addExecutionBlock:^{
            NSLog(@"下载4------%@", [NSThread currentThread]);
        }];
        
        [op start];
    }
    

    NSOperationQueue

    NSOperationQueue的作用
    NSOperation可以调用start方法来执行任务,但默认是同步执行的
    如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

    添加操作到NSOperationQueue中
    - (void)addOperation:(NSOperation *)op;
    - (void)addOperationWithBlock:(void (^)(void))block;
    
    最大并发数的相关方法
    - (NSInteger)maxConcurrentOperationCount;
    - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
    
    取消队列的所有操作
    - (void)cancelAllOperations;
    提示:也可以调用NSOperation的- (void)cancel方法取消单个操作
    
    暂停和恢复队列
    - (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列。 队列暂停时(当前正在执行任务是不会停止)(将要调度到线程里任务)
    (BOOL)isSuspended;(图片列表:加载+刷新)
    
    操作优先级

    设置NSOperation在queue中的优先级,可以改变操作的执行优先级

    - (NSOperationQueuePriority)queuePriority;
    - (void)setQueuePriority:(NSOperationQueuePriority)p;
    
    优先级的取值
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
    
    操作依赖

    NSOperation之间可以设置依赖来保证执行顺序
    比如一定要让操作A执行完后,才能执行操作B,可以这么写
    [operationB addDependency:operationA]; // 操作B依赖于操作A

    可以在不同queue的NSOperation之间创建依赖关系

    操作监听

    可以监听一个操作的执行完毕

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

    自定义NSOperation

    自定义NSOperation的步骤很简单
    重写- (void)main方法,在里面实现想执行的任务

    重写- (void)main方法的注意点
    自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
    经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

    针对上面的自定义NSOperation和NSOperationQueue有如下的demo

    首先自定义一个NSOperation,即新建一个类继承于NSOperation

    .h

    #import <Foundation/Foundation.h>
    
    @interface XMGOperation : NSOperation
    
    @end
    

    .m

    #import "XMGOperation.h"
    
    @implementation XMGOperation
    
    /**
     * 需要执行的任务  XMGOperation自定义的方法放在这里面,重写main
     */
    - (void)main
    {
        for (NSInteger i = 0; i<1000; i++) {
            //确保当前线程取消后能立即生效,视情况而定
            if (self.isCancelled) return;
            NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
        }
        
        //确保当前耗时线程取消后能生效,不执行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
    
    演示三种创建operation场景,并添加到NSOperationQueue中
    - (void)operationQueue1
    {
        // 创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        // 创建操作(任务)
        // 创建NSInvocationOperation
        NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
        NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
        
        // 创建NSBlockOperation  ,这个依旧子线程
        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]);
        }];
        
        // 创建自定义的operation对象 ,类名为XMGOperation,
        XMGOperation *op5 = [[XMGOperation alloc] init];
        
        // 添加任务到队列中
        [queue addOperation:op1]; // [op1 start] ,添加到队列中去,自动默认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]);
    }
    
    演示不用创建Blockoperation场景,直接给NSOperationQueue添加block
    - (void)operationQueue2
    {
        // 创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
     
        
    #warning 这两步可直接简化为给NSOperationQueue添加block
        // 创建操作
        //    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        //        NSLog(@"download1 --- %@", [NSThread currentThread]);
        //    }];
        
        // 添加操作到队列中
        //    [queue addOperation:op1];
        
        
        //上面两句作用 等同于 addOperationWithBlock
        
    #warning 也就是下面这句 addOperationWithBlock
        [queue addOperationWithBlock:^{
            NSLog(@"download1 --- %@", [NSThread currentThread]);
        }];
        [queue addOperationWithBlock:^{
            NSLog(@"download2 --- %@", [NSThread currentThread]);
        }];
    }
    
    演示NSOperationQueue并发数,设置为1直接就成串行了
    - (void)opetationQueue3
    {
        // 创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        // 设置最大并发操作数
        //    queue.maxConcurrentOperationCount = 2;
        queue.maxConcurrentOperationCount = 1; // 就变成了子线程的串行队列,
        
        // 添加操作
        [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];
        
        // 创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        // 设置最大并发操作数
        queue.maxConcurrentOperationCount = 1; // 就变成了串行队列
        
        // 添加操作
        [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 {
    //演示一 线程的暂停和继续
    // 如果在线程里做耗时操作,执行到一半,暂停是暂时不会执行的,直到这个耗时线程做完会暂停
        
    //    if (self.queue.isSuspended) {
    //        // 恢复队列,继续执行
    //        self.queue.suspended = NO;
    //    } else {
    //        // 暂停(挂起)队列,暂停执行
    //        self.queue.suspended = YES;
    //    }
     
    //演示二 线程的取消
        //把线程取消,直接消亡
        [self.queue cancelAllOperations];
        
    //eg:NSOperationQueue线程的暂停和取消功能,如果这个线程正在做一个耗时操作(比如遍历),暂停和取消会等到这个线程结束,才会结束queue,所以为了避免这种情况,自定义一个NSOperation(比如自己定义的XMGOperation),在耗时操作里监听取消和暂停状态,就是一个很好地将解决办法
    }
    
    
    演示线程依赖
    - (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线程完成以后调用completionBlock
        op5.completionBlock = ^{
            NSLog(@"op5执行完毕---%@", [NSThread currentThread]);
        };
        
        // 设置依赖 (再添加之前设置依赖)
        [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意味着当前方法会卡主,直到完成才会往下走
        [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的一些函数


    GCD的简单使用
    - (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];
    }
    
    /**
     * 同步函数 + 主队列:(任务会卡住)
     */
    - (void)syncMain
    {
        NSLog(@"syncMain ----- begin");
        
        // 1.获得主队列
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        // 2.将任务加入队列
        dispatch_sync(queue, ^{
            NSLog(@"1-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"2-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"3-----%@", [NSThread currentThread]);
        });
        
        NSLog(@"syncMain ----- end");
    }
    
    /**
     * 异步函数 + 主队列:只在主线程中执行任务 (这个特例说明了异步函数可以开启新线程,但并不是绝对可以开启新线程)
     */
    - (void)asyncMain
    {
        // 1.获得主队列
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        // 2.将任务加入队列
        dispatch_async(queue, ^{
            NSLog(@"1-----%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"2-----%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"3-----%@", [NSThread currentThread]);
        });
    }
    
    /**
     * 同步函数 + 串行队列:不会开启新的线程,在当前线程执行任务。任务是串行的,执 任务,再执行下一个任务
     */
    - (void)syncSerial
    {
        // 1.创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL);
        
        // 2.将任务加入队列
        dispatch_sync(queue, ^{
            NSLog(@"1-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"2-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"3-----%@", [NSThread currentThread]);
        });
    }
    
    /**
     * 异步函数 + 串行队列:会开启新的线程,但是任务是串行的,执行完一个任务,再执行下一个任务
     */
    - (void)asyncSerial
    {
        // 1.创建串行队列(串行队列只能创建)
        // DISPATCH_QUEUE_SERIAL 和 NULL 都会是串行队列
        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.将任务加入队列
        dispatch_async(queue, ^{
            NSLog(@"1-----%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"2-----%@", [NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"3-----%@", [NSThread currentThread]);
        });
    }
    
    /**
     * 同步函数 + 并发队列:不会开启新的线程(在主线程做)
     */
    - (void)syncConcurrent
    {
        // 1.获得全局的并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        // 2.将任务加入队列
        dispatch_sync(queue, ^{
            NSLog(@"1-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"2-----%@", [NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"3-----%@", [NSThread currentThread]);
        });
        NSLog(@"syncConcurrent--------end");
    }
    
    /**
     * 异步函数 + 并发队列:可以同时开启多条线程
     */
    - (void)asyncConcurrent
    {
        // 1.0创建一个并发队列
        // label : 相当于队列的名字  "com.520it.queue"
        // DISPATCH_QUEUE_CONCURRENT 队列类型,并发队列
        // dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);
        
        // 1.1直接获得全局的并发队列
        //DISPATCH_QUEUE_PRIORITY_DEFAULT优先级,作用:优先执行顺序
        //默认传0
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        // 2.将任务加入队列
        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);  现在GCD的create现在不需要释放了
    }
    
    @end
    
    队列组

    在使用GCD进行任务操作时,有时会希望若干个任务执行之间有先后执行的依赖关系。

    eg:

    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加异步任务
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务2-%@", [NSThread currentThread]);
        }
    });
    
    //等前面的任务执行完毕后,会自动执行这个任务
    dispatch_group_notify(group, queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"任务3-%@", [NSThread currentThread]);
            }
        });
    });
    /*下面这是大牛讲解时
    //等前面的任务执行完毕后,会自动执行这个任务
    dispatch_group_notify(group, dispatch_get_main_queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"任务3-%@", [NSThread currentThread]);
            }
        });
    });
    */
    
    dispatch_set_target_queue

    用法一:指定优先级
    ?

    用法二:控制队列执行阶层
    如果在多个SERIAL Dispatch Queue中用dispatch_set_target_queue函数制定目标为某一个SERIAL Dispatch Queue,那么原本应并行执行的多个SERIAL Dispatch Queue,在目标SERIAL Dispatch Queue上只能同时执行一个处理。如下:

    //1.创建目标队列
    dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
    
    //2.创建3个串行队列
    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个串行队列分别添加到目标队列
    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");
    });
    
    打印结果:
    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
    
    //如上,如果不设置targetQueue目标队列,那么queue1queue2queue3乱序跑。
    
    dispatch_after

    下面代码3秒之后执行,可以是任何线程

    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型
    //注意点,一旦调用dispatch_group_wait函数,该函数就处于调用状态而不返回。即是当前线程已经卡在这儿,不向后执行,必须等待前面任务处理有结果或者自己设定的dispatch_time_t时间结束。
    BOOL result =  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    if (result == 0) {//返回值为0,那么全部处理执行结束
        NSLog(@"done");
    }else{//如果时间过,但是前面的任务还没有跑完,会走到这儿
        NSLog(@"任务仍在执行");
    };
    
    dispatch_barrier_async
    //栅栏的队列不能使全局的并发,要是手动创建的并发  (待考证)
    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栅栏效果
    //官方解释:前面的执行完才到这,这个执行完才执行后面(★待考证)
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----5-----%@", [NSThread currentThread]);
    });
    
    dispatch_apply

    该函数按指定的次数将指定的block追加到指定的Dispatch Queue中,并等待全部处理结束。推荐在dispatch_async中非同步的执行dispatch_apply函数。

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSLog(@"----------------主线程开始------------");
        
        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次任务",index);
            });
            
            NSLog(@"done");
            
            //可以回到主线程进行通信了
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"刷新界面");
            });
        });
        
        NSLog(@"----------------主线程继续------------");
    }
    
    打印结果:
    2019-05-07 10:16:06.001245+0800 Test[2820:134508] ----------------主线程开始------------
    2019-05-07 10:16:06.001436+0800 Test[2820:134508] ----------------主线程继续------------
    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次任务
    2019-05-07 10:16:06.001637+0800 Test[2820:134543] 追加第1次任务
    2019-05-07 10:16:06.001700+0800 Test[2820:134543] 追加第2次任务
    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

    线程的挂起和恢复。dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
        //提交第一个block,延时2秒打印。
        dispatch_async(queue, ^{
            sleep(2);
            NSLog(@"After 2 seconds...");
        });
        //提交第二个block,也是延时2秒打印
        dispatch_async(queue, ^{
            sleep(2);
            NSLog(@"After 2 seconds again...");
        });
        //延时一秒
        NSLog(@"sleep 1 second...");
        sleep(1);
        //挂起队列
        NSLog(@"suspend...");
        dispatch_suspend(queue);
        //延时10秒
        NSLog(@"sleep 10 second...");
        sleep(10);
        //恢复队列
        NSLog(@"resume...");
        dispatch_resume(queue);
    }
    

    问题:GCD和nsopreation什么场景使用哪个好点


    GCD主要特点:

    1)GCG 是iOS4.0推出的,主要针对多核CPU 做了优化
    2)GCD是 C 语言的技术
    3)GCD 提供了一些 NSOperation 不具备的功能,比如一次性执行(创建单例),延迟执行,调度组.

    NSOperation 特点:

    1. NSOperation 是 iOS2.0后推出的,iOS4.0之后重写了NSOperation.
    2. NSOperation 将操作(异步的任务)添加到队列(并发队列),就会执行制定操作的函数.
    3. NSOperation里可以方便的设置操作:
      1⃣️最大并发数
      2⃣️队列的暂停/继续
      3⃣️取消所有的操作
      4⃣️指定操作之间的依赖关系(GCD可以用同步实现)
      **使用NSOperation 需要注意几点点:
    4. 注意避免产生循环依赖
    5. 要先设置依赖关系,然后添加到队列

    GCD 和 NSOperation的区别主要表现在以下几方面:

    1. GCD是一套 C 语言API,执行和操作简单高效,因此NSOperation底层也通过GCD实现,这是他们之间最本质的区别.因此如果希望自定义任务,建议使用NSOperation;

    2. 依赖关系,NSOperation可以设置操作之间的依赖(可以跨队列设置),GCD无法设置依赖关系,不过可以通过同步来实现这种效果;

    3. KVO(键值对观察),NSOperation容易判断操作当前的状态(是否执行,是否取消等),对此GCD无法通过KVO进行判断;
      (指的应该是:NSOperationQueue中使用了KVO,当NSOperation对象状态变化(finished,canceled等)时,观察者可以知道变化,从而做出相应的处理(移除已完成的等等))

    4. 优先级,NSOperation可以设置自身的优先级,但是优先级高的不一定先执行,GCD只能设置队列的优先级,如果要区分block任务的优先级,需要很复杂的代码才能实现;

    5. 继承,NSOperation是一个抽象类.实际开发中常用的是它的两个子类:NSInvocationOperation和NSBlockOperation,同样我们可以自定义NSOperation,GCD执行任务可以自由组装,没有继承那么高的代码复用度;

    6. 效率,直接使用GCD效率确实会更高效,NSOperation会多一点开销,但是通过NSOperation可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己;

    7)可以随时取消准备执行的任务(已经在执行的不能取消),GCD没法停止已经加入queue 的 block(虽然也能实现,但是需要很复杂的代码)

    基于GCD简单高效,更强的执行能力,操作不太复杂的时候,优先选用GCD;而比较复杂的任务可以自己通过NSOperation实现.


    问题:atomic


    atomic用于保证属性setter、getter的原子性操作,相当于在setter和getter内部加了线程同步的锁,从而保证了setter和getter内部是线程同步的。

    可以参考源码objc4的objc-accessors.mm
    首先看设置属性

    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);
    }
    

    点击查看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,可以看到加了一个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,可以看到加了一个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);
    }
    

    它并不能保证"使用属性"的过程是线程安全的。只是保证了set和get方法内部是线程安全的,但是并不能保证属性的过程是线程安全的。

    说atomic并不能保证使用属性的过程是线程安全的,

    举个例子
    1.对于NSArray类型 @property(atomic)NSArray *array我们用atomic修饰,数组的添加和删除并不是线程安全的,这是因为数组比较特殊,我们要分成两部分考虑,一部分是&array也就是这个数组本身,另一部分是他所指向的内存部分。atomic限制的只是&array部分,对于它指向的对象没有任何限制。
    eg:

    MJPerson *p = [[MJPerson alloc] init];//初始化类,类里面之前生命了一个可变数组dataArray属性
    p.dataArray = [NSMutableArray array];//初始化dataArray,这里面p.dataArray是线程安全的,因为调用了set方法
    [p.dataArray addObject:@"1"];  //这里面p.dataArray是线程安全的,因为调用了get方法,但是addObject却没有安全管控
    [p.dataArray addObject:@"2"];
    

    2、比如如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。

    如下

    @property (atomic, assign) NSInteger intA;
    
    - (void)viewDidLoad {
       [super viewDidLoad];
        
       //开启一个线程对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);
        });
        
       //开启一个线程对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
    打印:(因为都是异步,打印顺序并不固定)
    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:非原子性

    声明属性时不写的话默认就是atomic

    忙等

    自旋锁(Spinlock)是一种忙等待锁,线程反复检查锁变量是否可用,不会挂起,避免了进程上下文的调度开销,适合阻塞很短时间的场合。当然也就不适合单CPU单线程上使用。


    问题:dispatch_sync


    意味着将指定的block“同步”追加到指定的Dispatch Queue中,在追加block结束之前,dispatch_sync函数会一直等待。可以认为是简单版的dispatch_group_wait

    由于sync特性,所以使用sync函数往当前"串行"队列中添加任务,会卡住当前的串行队列(产生死锁)

    死锁的原因是:队列引起的循环等待。由于当前串行队列执行到dispatch_sync处等待dispatch_sync的返回,dispatch_sync必须等待block执行完毕后才能返回,由于当前队列是串行队列,先进先出,但是我们通过dispatch_sync新放入的block位于队列后面,现在得不到执行,所以锁住了。

    问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");
    

    原因:
    因为把同步的任务2添加到主队列,dispatch_sync要求立马在当前线程同步执行任务,走到这一步想要立即执行。想要执行队列里的任务2,必须要等前面一整个任务执行完。任务3在等任务2,任务2在等任务3。
    用图表示为

    当前线程      |         主队列
    任务1        |      viewDidLoad
    sync        |       任务2
    任务3        |
    
    
    那么把上述的dispatch_sync换成dispatch_async会不会产生死锁?不会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");
    

    原因:
    dispatch_async不要求立马在当前线程同步执行任务
    //打印结果为 执行任务1 执行任务3 执行任务2

    问题:以下代码会不会产生死锁?不会!
    • (void)viewDidLoad {
      [super viewDidLoad];
      NSLog(@"执行任务1");

      dispatch_async(dispatch_get_global_queue(0, 0), ^{
      //将上面放在主线程会死锁的代码放到子线程里不会死锁
      dispatch_queue_t queue = dispatch_get_main_queue();
      dispatch_sync(queue, ^{
      NSLog(@"执行任务2");
      });
      });

      NSLog(@"执行任务3");
      }

    问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });
        
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");
    
    问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });
        
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");
    
    问题:以下代码是在主线程执行的,会不会产生死锁?不会!虽是同步,但确实并发队列
    - (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.
    }
    
    下面代码执行会打印什么
    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");
        
        //添加这行代码,启动RunLoop程序才能打印2
        //[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    
    });
    }
    - (void)test
    {
        NSLog(@"2");
    }
    

    答:会打印 1 3,但是2不会打印。因为performSelector:withObject:afterDelay:这句代码的本质是往Runloop中添加定时器,而子线程的Runloop在这里没有启动。
    注意:- (id)performSelector:(SEL)aSelector withObject:(id)object;等不需要Runloop。

    下面代码执行会打印什么
    - (void)test
    {
        NSLog(@"2");
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSThread *thread = [[NSThread alloc] initWithBlock:^{
            NSLog(@"1");
            
            //启动RunLoop,保住线程。然后程序正常运行,打印了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];
    }
    

    答:会打印 1 ,然后程序崩溃。因为执行完1,block结束后线程结束。


    问题:信号量


    dispatch_semaphore

    semaphore叫做”信号量”
    信号量的初始值,可以用来控制线程并发访问的最大数量
    信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

    //定义一个控制最大量的值
    int value= 1;
    //初始化信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
    //执行到这句话时,如果信号量的值<=0,当前线程会进入睡眠等待(直到信号量的值>0);如果信号量的值>0,就减1,继续往下执行。
    dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
    //让信号量的值加1
    dispatch_semaphore_signal(semaphore);
    

    信号量的使用场景

    加锁
    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);
    
            //临界区,即待加锁的代码区域
    
            dispatch_semaphore_signal(semaphore);
        });
    }
    

    在进行多线程任务之前,首先创建一个计数为1的信号量,这样可以保证同一时刻只有一个线程在访问临界区。上面代码,系统会同时开启很多线程想要做遍历里面的事,但是同时只有一个线程能去做临界区的事情。

    异步任务,同步返回
    - (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赋值,代码有点长,就不贴了
            dispatch_semaphore_signal(semaphore);
        }];
    
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
        return tasks;
    }
    

    上面是 AFNetworking 的一段代码,我且称之为异步去做,同步返回。这段代码的功能是通过异步的请求取得键路径为 keyPath 的任务数组 tasks,然后同步返回它。

    控制线程并发数

    在 GCD 中,dispatch_async() 异步操作可以产生新的线程,但是方法本身没办法限制线程的最大并发数,线程的创建和销毁是由 GCD 底层管理的。
    了解 NSOperationQueue 的同学肯定知道,通过 maxConcurrentOperationCount 属性可以设置它的最大并发数。那么在GCD中,对应的解决方法就是使用信号量。

    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);
        });
    }
    

    其实跟加锁代码非常相似,区别在于,在初始化信号量时,将计数赋值为最大并发数。在应用场景上,限制线程并发数是为了性能考虑,而加锁是为了安全而考虑。

    信号量和栅栏函数是锁吗

    信号量是属于很多种加锁方式的一种,栅栏函数能实现读写锁的功能,所以应该都属于锁。


    问题:线程池


    线程池是一种线程使用模式。 线程过多会带来调度开销,进而影响缓存局部性和整体性能。 而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。 这避免了在处理短时间任务时创建与销毁线程的代价。

    线程池的执行流程如图下:
    1612430117402.jpg

    若线程池大小小于核心线程池大小时
    创建线程执行任务

    若线程池大小大于等于核心线程池大小时
    1.先判断线程池工作队列是否已满
    2.若没满就将任务push进队列
    3.若已满时,且maximumPoolSize>corePoolSize,将创建新的线程来4.执行任务
    反之则交给饱和策略去处理

    参数名代表意义

    corePoolSize
    线程池的基本大小(核心线程池大小)

    maximumPool
    线程池的最大大小

    keepAliveTime
    线程池中超过corePoolSize树木的空闲线程的最大存活时间

    unit
    keepAliveTime参数的时间单位

    workQueue
    任务阻塞队列

    threadFactory
    新建线程的工厂

    handler
    当提交的任务数超过maxmumPoolSize与workQueue之和时,任务会交给RejectedExecutionHandler来处理

    上图的解释

    我们知道,受限于硬件、内存和性能,我们不可能无限制的创建任意数量的线程,因为每一台机器允许的最大线程是一个有界值。也就是说ThreadPoolExecutor管理的线程数量是有界的。线程池就是用这些有限个数的线程,去执行提交的任务。然而对于多用户、高并发的应用来说,提交的任务数量非常巨大,一定会比允许的最大线程数多很多。为了解决这个问题,必须要引入排队机制,或者是在内存中,或者是在硬盘等容量很大的存储介质中。J.U.C提供的ThreadPoolExecutor只支持任务在内存中排队,通过BlockingQueue暂存还没有来得及执行的任务。

    任务的管理是一件比较容易的事,复杂的是线程的管理,这会涉及线程数量、等待/唤醒、同步/锁、线程创建和死亡等问题。ThreadPoolExecutor与线程相关的几个成员变量是:keepAliveTime、allowCoreThreadTimeOut、poolSize、corePoolSize、maximumPoolSize,它们共同负责线程的创建和销毁。
    corePoolSize:
    线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。这里需要注意的是:在刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。再考虑到keepAliveTime和allowCoreThreadTimeOut超时参数的影响,所以没有任务需要执行的时候,线程池的大小不一定是corePoolSize。

    maximumPoolSize:
    线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变运行的最大线程的数目。

    poolSize:
    线程池中当前线程的数量,当该值为0的时候,意味着没有任何线程,线程池会终止;同一时刻,poolSize不会超过maximumPoolSize。

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


    问题:异步执行ABC之后在执行D的正确理解


    首先异步执行ABC分两种情况,一种任务是同步的,一种任务是网络请求,发送操作是同步的,但是请求到的结果是异步的。

    任务是同步时

    第一种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(@"同步任务A");
        });
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"同步任务B");
        });
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"同步任务C");
        });
        
        dispatch_group_notify(group, queue, ^{
            NSLog(@"任务完成执行");
        });
    
    第二种 dispatch_barrier_async
    dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            NSLog(@"任务A");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务B");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务C");
        });
        
        dispatch_barrier_async(queue, ^{
            NSLog(@"阻塞自定义并发队列");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务D");
        });
    
        dispatch_async(queue, ^{
            NSLog(@"任务E");
         });
    
    第三种 NSOperation
    NSBlockOperation *operatioon1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务A");
        }];
        
        NSBlockOperation *operatioon2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务B");
        }];
        
        NSBlockOperation *operatioon3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务C");
        }];
        
        NSBlockOperation *operatioon4 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"任务D");
        }];
        
        [operatioon4 addDependency:operatioon1];
        [operatioon4 addDependency:operatioon2];
        [operatioon4 addDependency:operatioon3];
        
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue addOperations:@[operatioon1,operatioon2,operatioon3,operatioon4] waitUntilFinished:YES];
        NSLog(@"完成之后的操作");
    

    任务时异步时(比如网络请求异步任务)

    第一种 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(@"同步任务A");
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"网络异步任务一");
                dispatch_semaphore_signal(semaphore);
            });
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        });
        
        dispatch_group_async(group, queue, ^{
            
            NSLog(@"同步任务B");
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"网络异步任务二");
                dispatch_semaphore_signal(semaphore);
            });
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        });
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"同步任务C");
        });
        
        dispatch_group_async(group, queue, ^{
            
            NSLog(@"同步任务D");
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"网络异步任务四");
                dispatch_semaphore_signal(semaphore);
            });
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        });
        
        dispatch_group_notify(group, queue, ^{
            NSLog(@"任务完成执行");
        });
    
    
    第二种 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(@"同步任务A");
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"网络异步任务一");
                dispatch_group_leave(group);
            });
        });
        
        dispatch_group_enter(group);
        dispatch_group_async(group, queue, ^{
            
            NSLog(@"同步任务B");
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"网络异步任务二");
                dispatch_group_leave(group);
            });
        });
        dispatch_group_enter(group);
        dispatch_group_async(group, queue, ^{
            NSLog(@"同步任务C");
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_group_async(group, queue, ^{
            
            NSLog(@"同步任务D");
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"网络异步任务四");
                dispatch_group_leave(group);
            });
        });
        
        dispatch_group_notify(group, queue, ^{
            NSLog(@"任务完成执行");
        });
    

    相关文章

      网友评论

        本文标题:iOS 线程相关面试题

        本文链接:https://www.haomeiwen.com/subject/uhyptltx.html