博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
我的同事金司机出的 5 道 iOS 多线程“面试题”
阅读量:6831 次
发布时间:2019-06-26

本文共 6347 字,大约阅读时间需要 21 分钟。

我有一个同事,他既不姓金,也不是司机,但我们都叫他“金司机”。他跟仓鼠一样是一个 iOS 工程师,至于叫司机的原因就不难想到了…… 为了防止博客被封,在此不举例子。

总之,金司机在这周周会上给组里同事展示了好几道他出的“面试题”,成功淘汰了组里所有同事、甚至包括我们老大,给平淡的工作带来了许多欢乐。之所以打引号,是因为这些题只是形式像面试题,其实并不能真的用来面试(而且我们公司绝不会使用这些题来面试),不然恐怕一个人都招不到了。大家有兴趣看看就好,不许喷我同事~

代码是在 command line 环境下执行的,虽然代码是 swift 写的,不过 API 都是一样的,写 Objective-C 的朋友也能一看就懂。我们开始吧~

主线程与主队列

在看这组题之前,先问自己一个问题:主线程和主队列的关系是什么?

第一题

let key = DispatchSpecificKey
()DispatchQueue.main.setSpecific(key: key, value: "main")func log() { debugPrint("main thread: \(Thread.isMainThread)") let value = DispatchQueue.getSpecific(key: key) debugPrint("main queue: \(value != nil)")}DispatchQueue.global().sync(execute: log)RunLoop.current.run()复制代码

执行结果是什么呢?

第二题

let key = DispatchSpecificKey
()DispatchQueue.main.setSpecific(key: key, value: "main")func log() { debugPrint("main thread: \(Thread.isMainThread)") let value = DispatchQueue.getSpecific(key: key) debugPrint("main queue: \(value != nil)")}DispatchQueue.global().async { DispatchQueue.main.async(execute: log)}dispatchMain()复制代码

什么情况下输出的结果并不是两个 true 呢?

GCD 与 OperationQueue

第三题

let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0) { _, activity in  if activity.contains(.entry) {    debugPrint("entry")  } else if activity.contains(.beforeTimers) {    debugPrint("beforeTimers")  } else if activity.contains(.beforeSources) {    debugPrint("beforeSources")  } else if activity.contains(.beforeWaiting) {    debugPrint("beforeWaiting")  } else if activity.contains(.afterWaiting) {    debugPrint("afterWaiting")  } else if activity.contains(.exit) {    debugPrint("exit")  }}CFRunLoopAddObserver(CFRunLoopGetMain(), observer, CFRunLoopMode.commonModes)// case 1DispatchQueue.global().async {  (0...999).forEach { idx in    DispatchQueue.main.async {      debugPrint(idx)    }  }}// case 2//DispatchQueue.global().async {//  let operations = (0...999).map { idx in BlockOperation { debugPrint(idx) } }//  OperationQueue.main.addOperations(operations, waitUntilFinished: false)//}RunLoop.current.run()复制代码

上面 GCD 的写法,和被注释掉的 OperationQueue 的写法,print 出来会有什么不同呢?

线程安全

第四题

这个题 Objective-C 和 swift 会有些不一样,所以我提供了两个版本的代码:

Swift:

let queue1 = DispatchQueue(label: "queue1")let queue2 = DispatchQueue(label: "queue2")var list: [Int] = []queue1.async {  while true {    if list.count < 10 {      list.append(list.count)    } else {      list.removeAll()    }  }}queue2.async {  while true {    // case 1    list.forEach { debugPrint($0) }    // case 2//    let value = list//    value.forEach { debugPrint($0) }    // case 3//    var value = list//    value.append(100)  }}RunLoop.current.run()复制代码

使用 case 1 的代码会 crash 吗?case 2 呢?case 3 呢?

Objective-C:

dispatch_queue_t queue1 = dispatch_queue_create("queue1", 0);    dispatch_queue_t queue2 = dispatch_queue_create("queue2", 0);        NSMutableArray* array = [NSMutableArray array];    dispatch_async(queue1, ^{      while (true) {        if (array.count < 10) {          [array addObject:@(array.count)];        } else {          [array removeAllObjects];        }      }    });    dispatch_async(queue2, ^{      while (true) {        // case 1//        for (NSNumber* number in array) {//          NSLog(@"%@", number);//        }        // case 2//        NSArray* immutableArray = array;//        for (NSNumber* number in immutableArray) {//          NSLog(@"%@", number);//        }        // case 3        NSArray* immutableArray = [array copy];        for (NSNumber* number in immutableArray) {          NSLog(@"%@", number);        }      }    });    [[NSRunLoop currentRunLoop] run];复制代码

使用 case 1 的代码会 crash 吗?case 2 呢?case 3 呢?

Runloop

第五题

class Object: NSObject {  @objc  func fun() {    debugPrint("\(self) fun")  }}var runloop: CFRunLoop!let sem = DispatchSemaphore(value: 0)let thread = Thread {  RunLoop.current.add(NSMachPort(), forMode: .commonModes)  runloop = CFRunLoopGetCurrent()  sem.signal()  CFRunLoopRun()}thread.start()sem.wait()DispatchQueue.main.asyncAfter(deadline: .now() + 1) {  CFRunLoopPerformBlock(runloop, CFRunLoopMode.commonModes.rawValue) {    debugPrint("2")  }  DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {    debugPrint("1")    let object = Object()    object.fun()//    CFRunLoopWakeUp(runloop)  })}RunLoop.current.run()复制代码

这样会输出什么呢?

答案

第一题:

"main thread: true""main queue: false"复制代码

看到主线程上也可以运行其他队列。

第二题: 这道题要想出效果比较不容易。所以放一张截图:

看,主队列居然不在主线程上啦!

这里用的这个 API dispatchMain() 如果改成 RunLoop.current.run(),结果就会像我们一般预期的那样是两个 true。而且在 command line 环境下才能出这效果,如果建工程是 iOS app 的话因为有 runloop,所以结果也是两个 true 的。

第三题: GCD:

"entry""beforeTimers""beforeSources""beforeWaiting""afterWaiting""exit""entry""beforeTimers""beforeSources""beforeWaiting""afterWaiting"01234...996997998999"exit""entry""beforeTimers""beforeSources""beforeWaiting""afterWaiting""exit""entry""beforeTimers""beforeSources""beforeWaiting"复制代码

OperationQueue

"entry""beforeTimers""beforeSources""beforeWaiting""afterWaiting"0"exit""entry""beforeTimers""beforeSources""beforeWaiting""afterWaiting"1"exit""entry""beforeTimers""beforeSources""beforeWaiting""afterWaiting"2"exit""entry""beforeTimers""beforeSources""beforeWaiting""afterWaiting"...复制代码

这个例子可以看出有大量任务派发时用 OperationQueue 比 GCD 要略微不容易造成卡顿一些。

第四题: 这个题其实还挺实用的,答案是两种语言的每个 case 都会 >< [NSArray copy] 那个概率低一点儿,但是稍微跑一会儿还是很容易触发的。

感谢楼下评论的朋友,补充一句:Objective-C 的第三个 case 跟前两个 crash 的原因确实是不一样的,error message 是 release 一个已经 release 的东西。至于为啥会这样我也不知道,问题应该在 copy 方法的内部实现里吧。

第五题: 上面的代码直接运行出来是

"1""
fun"复制代码

如果把 object.fun() 改成 object.perform(#selector(Object.fun), on: thread, with: nil, waitUntilDone: false) 的话就能 print 出来 2 了,就是说 runloop 在 sleep 状态下,performSelector 是可以唤醒 runloop 的,而一次单纯的调用不行。

有一个细节就是,如果用CFRunLoopWakeUp(runloop)的话,输出顺序是1 fun 2 而用 performSelector 的话顺序是 1 2 fun。我的朋友骑神的解释:

perform调用时添加的timer任务会唤醒runloop去处理任务。但因为CFRunLoopPerformBlock的任务更早加入队列中,所以输出优先于fun

题解

仓鼠本来想厚颜无耻地写一篇付费文章,然后把题解部分作为付费部分,估计肯定赚一波小钱:)但是因为仓鼠比较菜,心虚怕会说错,所以我就不提供题解啦~ 欢迎大家在评论区讨论吧,我也会放出朋友们的解答链接~~

后记

仓鼠公司也在招人。因为以前写博客被喷过,至今心有余悸;所以怕公司被喷,我不敢说是哪个公司了(有这么招人的吗?) 总之就是一个外企互联网公司,坐标北京。大部分 swift,很显然我的同事和老大技术水平都非常强,仓鼠在这是最菜的。而且大家都特别 nice,公司福利待遇也是业内顶尖水平的。我们的面试题非常注重实操,主要都是现场写代码实现小功能,100% 是平常工作最常使用的,绝不会使用上面这些奇奇怪怪的题,大家可以放心。要求的话,现阶段只招比较 senior 的人,基本上要求真的有 4 年以上的经验,有大厂的经历或者学校背景好的话会比较好~ 不用太担心对语言的要求,不会 swift 是没问题的,英语也不是大问题。有兴趣的朋友欢迎私信仓鼠,我可以解答关于工作和面试的各种问题~ 如果是因为这篇文章带来的推荐奖,我也会全部转给我的同事金司机,说到做到:)

转载地址:http://nnnkl.baihongyu.com/

你可能感兴趣的文章
LVS DR模式负载均衡配置详解(配置篇一)
查看>>
我的友情链接
查看>>
OPENSSH 7.6SP1升级
查看>>
linux:ip命令
查看>>
YOU MIGHT NOT NEED JQUERY
查看>>
vmware workstation安装与卸载
查看>>
Vue 2.0生命周期和钩子函数
查看>>
使用Sentinel机制实现Redis高可用主从复制
查看>>
Python基础:运算符
查看>>
通过Python脚本理解系统进程间通信
查看>>
PHP生成PDF文件类库大全[开源]
查看>>
KVM与Xen两大虚拟化的比较
查看>>
【红帽认证参考】常见问题解答
查看>>
Poco官方PPT_000-IntroAndOverview双语对照翻译
查看>>
Poco官方PPT_010-Types双语对照翻译
查看>>
路由基础
查看>>
java二叉排序树 查找 插入 求父节点 算法
查看>>
zabbix有关网站
查看>>
Android MVC实现一个音乐播放器
查看>>
PySNMP学习笔记(一)
查看>>