iOS基础深入补完计划--多线程(面试题)汇总

(由于合在一起感觉一篇太长翻着累)

iOS多线程目前总结了四篇

欢迎移步O(∩_∩)O

说是面试题汇总、但实际上是我个人如果聊到多线程可能会话赶话聊到的问题。如果有新的问题、欢迎评论留言。

目录

  • 实际问题

    • 子线程同时执行ABC三个同步任务、全部执行完成再在子线程执行三个同步任务EDF。
    • 上一题中的ABC三个任务改成异步任务(如AFN网络请求)、全部回调成功后进行数据整合。
    • 实现本地大体量数组内容的实时输入搜索(如通讯录搜索好友名称/ID)。
  • 问题汇总

    • 并行和并发的区别?
    • 串行/并行、同步异步的区别?(附带如何判断GCD的执行顺序、是否开辟线程)
    • NSOperationGCD的关系
    • 默认最大并发
    • 线程取消
    • [thread cancel]可以关闭线程?
    • performSelector开头那么多方法为什么分散在不同的文件里?
    • NSBlockOperationNSInvocationOperation有什么关系和区别。
    • NSInvocationOperation如何解决参数受限的问题
    • NSOperation可以像GCD一样设置串行并行么?
    • NSOperation队列内操作执行的时间点
    • NSOperation设置优先级是否可以直接决定操作的执行顺序?
    • NSBlockOperationaddExecutionBlock 追加的操作、是否为串行执行。如果不是、为什么要这么设计?
    • 主队列([NSOperationQueue mainQueue])可以不可以修改最大并发数?主队列下添加的操作、都会在主线程执行么?
    • GCD的并行队列一定会开辟新的线程?
    • dispatch_once如何实现一次性代码?
    • NSOperation的添加进队列后可不可以追加依赖?GCD任务组添加监听后可不可以追加任务?
  • 不同线程对比


实际问题

这个不太可能让人真的手写代码或者上机、根据回答的点大概可以揣测一些水平吧?

  • 子线程同时执行ABC三个同步任务、全部执行完成再在子线程执行三个同步任务EDF。

说队列组/依赖基本可以确定了解GCD/NSOpertion。但是比较麻烦、用线程栅栏dispatch_barrier的话会更简便一些。

  • 上一题中的ABC三个任务改成异步任务(如AFN网络请求)、全部回调成功后进行数据整合。

如果只说队列/任务组肯定不行。因为网络请求本身是异步的、任务会立即完成、但数据还没有回来。这样就有两种方式
1、把异步的网络请求转化为同步、以捕获正确的完成时机。
具体操作需要使用信号量
2、是使用dispatch_group_enter以及dispatch_group_leave的搭配。
这个要感谢评论区《9b298c9c5162》的提示。

  • 实现本地大体量数组内容的实时输入搜索(如通讯录搜索好友名称/ID)

1、每次输入字符的时候。如何进行数据遍历(如果用的For、那么为什么不用GCD的快速迭代?----因为开辟线程以及线程同步需要些许耗时、对于非耗时操作、for的性能会更好一些)。
2、每次输入字符的时候。如何废弃之前的搜索任务、以免重复插入(NSOperationQueue之类)。
3、高频次使用searchArray数组时的安全性。(锁)

问题汇总

  • 并行和并发的区别?
    • 并发是指两个或多个事件在同一时间间隔内发生。
      例如单CPU的处理多线程。
    • 并行是指两个或者多个事件在同一时刻发生
      例如多CPU的处理多线程。
  • 串行/并行、同步异步的区别?(附带如何判断GCD的执行顺序、是否开辟线程)
    • (同步/异步)任务
      决定代码块是否会阻塞当前线程、并且插入指定队列的末尾执行。

    • (串行/并行/主)队列

  1. 异步任务下、(串行/并行/主)队列决定将由哪条队列的线程执行代码块。
    串行队列/主队列维护一条线程、并行队列维护多条线程。

  2. 同步任务下、(串行/并行)队列使得代码块一定是在当前线程执行(主队列则交给主队列执行)、但要考虑死锁。

  • 死锁的条件
    1. 在一个串行队列维护的线程内、让该串行队列执行同步任务。
    最经典的就是、在主线程内让主线程执行同步任务。
    既阻塞了当前线程、又想在当前线程末尾执行。

这里不管你中间怎么使用、只通过线程和队列就能判断。

  1. 比较奇葩一个循环内、连续的同步任务串行下出现两次相同的串行队列。
    感觉没人会这么写、除非是神经病。

但如果其中修改一个为并行对列、或者异步任务。就没有问题。

判断最终结果时:先看任务类型、然后看队列情况。

  • NSOperationGCD的关系

GCD基于C、NSOperation基于GCD的封装。

  • 默认最大并发
    • NSThread
      本身并不会限制、也不支持限制最大并发(起码支持是四位数以内、如果超过某个阈值会error[NSThread start]: Thread creation failed with error 35)
- (void)thread_bingfa {
    for (int a = 0; a < 1000; a ++) {
        [self performSelectorInBackground:@selector(aaa:) withObject:[NSNumber numberWithInt:a]];
    }
}

- (void)aaa:(NSNumber *)number {
    for (int a = 0; a < 100; a++) {
        sleep(1);
        NSLog(@"%@",number);
    }
}
最大并发_NSThread
  • NSOperation
    默认的限制大概三位数以下(我模拟器分配到了63)
    self.operationQueue=[[NSOperationQueue alloc]init];

- (void)viewDidLoad {
    for (int a = 0; a < 10000; a ++) {
        [self bbb:a];
    }
}

- (void)bbb:(int)a {

        [self.operationQueue addOperationWithBlock:^{
            for (int i = 0; i < 100; i ++) {
                sleep(10);
                NSLog(@"%d",a);
            }
        }];
}
最大并发_NSOperation
  • GCD
    默认的最大并发和NSOperation相同。
    毕竟NSOperation基于GCD的OC封装、倒也说得通。
  • [thread cancel]可以关闭线程?

不能、只能把对应线程进行cancel标记。详见下文NSThread相关的几个坑

  • performSelector开头那么多方法为什么分散在不同的文件里?

分散在NSThread.hNSRunLoop.hNSObject.h
详见下文一些NSObject的相关扩展方法(performSelector).

  • NSBlockOperationNSInvocationOperation有什么关系和区别。
    • 二者都是NSOperation的子类、都可以被添加进队列中(或者自己主动)执行。
    • NSBlockOperation可以解决NSInvocationOperation传递参数受限的问题。
  • NSInvocationOperation如何解决参数受限的问题

这个问题其实和解决- (id)performSelector:(SEL)aSelector withObject:(id)object下方法受限的方式一样。
1、使用字典。
2、NSInvocationOperation实际上就是方法签名NSInvocation。所以如果使用NSInvocation进行初始化也能解决参数受限的问题、只是太麻烦了、除非特定情况(目前我接触到的只有模块化的Route层)不然不推荐。

  • NSOperation可以像GCD一样设置串行并行么?

串行并行实际上是GCD的名词。
并行意味着多线程执行任务、串行意味着单线程执行任务。
任务在每一个线程内部、其实都是串行的。
NSOperation并没有串行并行的概念、自然也谈不上设置。
但是我们可以通过通过设置某个队列(NSOperationQueue)的大并发数为1、让其中任务们(NSOperation)自动被分配到不同线程中自动执行、以达到串行/并行的底层结果。

  • NSOperation队列内操作执行的时间点:
    • 所有操作在被添加到队列中时、立即进行如下判断:
      • 如果所插入的操作存在依赖关系、优先完成依赖操作。
      • 如果所插入的操作不存在依赖关系、队列并发数为1下采用先进先出的原则、反之直接开辟新的线程执行。
        (具体可见下文NSOperation --> 操作的执行顺序
    • 当一个操作执行完成之后、队列会取出对其有依赖的所有操作、进行下一步判断:
      • 如果该操作没有其他依赖(准备就绪、isReady属性)、进行下一步判断
      • 所有可以执行的操作根据优先级排序执行。
        (具体可见下文NSOperation --> 操作的优先级
  • NSOperation设置优先级是否可以直接决定操作的执行顺序?

不能、优先级的判定是建立在依赖操作完成后对下一步操作的排序下。
具体可见下文NSOperation --> 操作的优先级

  • NSBlockOperationaddExecutionBlock 追加的操作、是否为串行执行。如果不是、为什么要这么设计?

不是、默认的操作会被置于队列开辟的首个线程(主队列则为主线程)、剩余的操作会开辟新的线程并发执行。但是有并发数限制、由系统分配。
至于为什么这么设计。
NSBlockOperation下所有的操作默认情况下也是并行的。由并行通过依赖控制成串行容易、但由由串行想做出并行的效果则很难。
比如需要同时下载三张图片下载完成之后、将其展示。
我可以将三个下载操作追加进一个blockOperation1、再让展示操作的BlockOperation2依赖BlockOperation1。

    NSOperationQueue *operationQueue=[NSOperationQueue mainQueue];
    
    NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"下载任务--%d",1);
    }];

    for (int i=2; i<4; ++i) {
        [blockOperation1 addExecutionBlock:^{
            sleep(1);
            NSLog(@"下载任务--%d",i);
        }];
    }
    
    NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"展示任务");
    }];
    [blockOperation2 addDependency:blockOperation1];
    
    
    [operationQueue addOperation:blockOperation1];
    [operationQueue addOperation:blockOperation2];

打印结果

2018-03-16 16:34:02.388806+0800 test[5620:522718] 下载任务--2
2018-03-16 16:34:02.388806+0800 test[5620:522602] 下载任务--1
2018-03-16 16:34:02.388806+0800 test[5620:522717] 下载任务--3
2018-03-16 16:34:03.390790+0800 test[5620:522602] 展示任务

如果addExecutionBlock的操作是串行的。那么我只能创建三个下载操作、然后将展示操作依赖于以上三个操作。得不偿失。

  • 主队列([NSOperationQueue mainQueue])可以不可以修改最大并发数?主队列下添加的操作、都会在主线程执行么?
    • 不能、主队列的最大并发数始终为1(自定义队列默认为-1)、且修改无效。
    • 默认状况下是的、但也有例外(追加操作addExecutionBlock)。
  • GCD的并行队列一定会开辟新的线程?


GCD

  • dispatch_once如何实现一次性代码?

详情可以查阅GCD-->一次性代码(单例)

  • NSOperation的添加进队列后可不可以追加依赖?GCD任务组添加监听后可不可以追加任务?
    • NSOperation的依赖必须在添加进队列(并且执行前)之前设置。(但是我们可以对某被依赖的操作进行追加addExecutionBlock以延缓调用)
    • GCD任务组则具备追加任务的功能。前提是监听并未被触发。

具体可以详见相应下文NSOperation --> 队列插入操作后的执行顺序下文GCD -- > 队列组


不同线程对比

主要说GCD和NSOperation、如果NSThread方便实现的话可能会提一句。

线程切换
  • NSThread
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
  
    
    [self performSelectorInBackground:@selector(fun1) withObject:nil];
}

- (void)fun1 {
    //回到主线程
    [self performSelectorOnMainThread:@selector(fun2) withObject:nil waitUntilDone:nil];
}
  • GCD
    创建/获取一个并行队列添加任务、然后返回主队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue, ^{
    // 执行耗时的异步操作...
    dispatch_async(dispatch_get_main_queue(), ^{
      // 回到主线程,执行UI刷新操作
    });
});
  • NSOperation
    创建队列添加操作、然后返回主队列
NSOperationQueue * queue = [[NSOperationQueue alloc]init];

//切换到子线程
[queue addOperationWithBlock:^{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        //切换回主线程
    }];
}];
  • 队列组/依赖
  • GCD
- (void)dispatch_group_test {
    
    dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务1——准备休眠3秒");
        sleep(3);
        NSLog(@"任务1——完成");
    });
    
    NSLog(@"主线程——准备休眠5秒");
    sleep(5);
    NSLog(@"主线休眠结束");
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务2——准备休眠10秒");
        sleep(10);
        NSLog(@"任务2——完成");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任务组完成");
    });

    NSLog(@"主线程结束");
}
  • NSOperation
//创建操作队列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
//创建最后一个操作
NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
    sleep(1);
    NSLog(@"最后的任务");
}];
for (int i=0; i<5-1; ++i) {
    //创建多线程操作
    NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
        sleep(i);
        NSLog(@"第%d个任务",i);
    }];
    //设置依赖操作为最后一个操作
    [blockOperation addDependency:lastBlockOperation];
    [operationQueue addOperation:blockOperation];
    
}
//将最后一个操作加入线程队列
[operationQueue addOperation:lastBlockOperation];
  • GCD
    • 适用于多个任务同时执行、可以捕获所有任务完成的回调。
    • 任务添加到队列组、且未全部完成时可以向任务组中添加任务。
  • NSOperation
    • 适用于多个任务之间相互依赖等待、最后完成的时候并没有回调。
    • 添加到队列中(并且已经执行)的操作不能再新增依赖、但是可以向追加操作。
串行队列
  • GCD
    两种方式获取/创建
//主队列--串行
dispatch_queue_t queue1 = dispatch_get_main_queue();
//自定义串行队列
dispatch_queue_t queue2 = dispatch_queue_create("test_queue", DISPATCH_QUEUE_SERIAL);
  • NSOperation
    两种方式获取/创建
//主队列
NSOperationQueue * queue1 = [NSOperationQueue mainQueue];
//自定义队列 -- 把并发改为1
NSOperationQueue * queue2 = [[NSOperationQueue alloc]init];
queue2.maxConcurrentOperationCount = 1;
最大并发
  • GCD
    通过信号量进行约束。详见GCD-->信号量
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建信号量,并且设置值为3
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_group_async(group, queue, ^{
        NSLog(@"%i",i);
        sleep(2);
        // 每次发送信号则semaphore会+1,
        dispatch_semaphore_signal(semaphore);
    });
}
  • NSOperation
    直接设置
NSOperationQueue * queue2 = [[NSOperationQueue alloc]init];
queue2.maxConcurrentOperationCount = 3;

最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 本文首发于我的个人博客:「程序员充电站」[https://itcharge.cn]文章链接:「传送门」[https...
    ITCharge阅读 347,257评论 308 1,925
  • 从哪说起呢? 单纯讲多线程编程真的不知道从哪下嘴。。 不如我直接引用一个最简单的问题,以这个作为切入点好了 在ma...
    Mr_Baymax阅读 2,734评论 1 17
  • 背景 担心了两周的我终于轮到去医院做胃镜检查了!去的时候我都想好了最坏的可能(胃癌),之前在网上查的症状都很相似。...
    Dely阅读 9,226评论 21 42
  • ————如果必须回忆,就用遗忘的方式 自二十四桥的洞孔出发 饮尽最后一杯花酒 那时辰 尚有剩余的...
    高天洁雨18阅读 446评论 5 9
  • 游子吟 【唐·孟郊】 慈母手中线, 游子身上衣。 临行密密缝, 意恐迟迟归。 谁言寸草心, 报得三春晖。 游子吟:...
    静竹筱阅读 306评论 0 0