ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

阿里、字节:一套高效的iOS面试题之多线程

2020-12-07 15:31:48  阅读:291  来源: 互联网

标签:面试题 队列 iOS dispatch queue 线程 多线程 block once


前言

本文具有强烈的个人感情色彩,如有观看不适,请尽快关闭. 本文仅作为个人学习记录使用,也欢迎在许可协议范围内转载或使用,请尊重版权并且保留原文链接,谢谢您的理解合作. 如果您觉得本站对您能有帮助,您可以使用RSS方式订阅本站,这样您将能在第一时间获取本站信息.

本篇我们来讲一下 阿里、字节:一套高效的iOS面试题 中的多线程相关的问题.

多线程

这一篇我们来解答下多线程问题,主要以GCD为主:

  • iOS开发中有多少类型的线程?分别对比
  • GCD有哪些队列,默认提供哪些队列
  • GCD有哪些方法api
  • GCD主线程 & 主队列的关系
  • 如何实现同步,有多少方式就说多少
  • dispatch_once实现原理
  • 什么情况下会死锁
  • 有哪些类型的线程锁,分别介绍下作用和使用场景
  • NSOperationQueue中的maxConcurrentOperationCount默认值
  • NSTimer、CADisplayLink、dispatch_source_t 的优劣

1.iOS开发中有多少类型的线程?分别对比

线程类型对比备注
pthread_t跨平台C语言标准库中的多线程框架过于底层使用很麻烦,需要封装使用.
GCD(Grand Central Dispatch)iOS5后苹果推出的双核CPU优化的多线程框架,对A5以后的CPU有很多底层优化,C函数的形式调用 有点面向过程,不能直接设置并发数,需要写一些代码曲线方式实现并发推荐使用
NSOperation & NSOperationQueue更加面向对象 可以设置并发数量GCD 的封装

苹果底层库经过自己多年实践没有问题才会推荐给上层使用, eg:siri. 所以NSOperation实际上是苹果的ver1.0的多线程SDK,对GCD封装和pthread_t的封装.

2.GCD有哪些队列,默认提供哪些队列

  • 1.主线程串行队列
  • 2.全局并行队列
  • 3.自定义队列(可自行设置串/并的参数DISPATCH_QUEUE_SERIALDISPATCH_QUEUE_CONCURRENT)

下面我整理了一个表格:
这里有一个iOS交流圈:891 488 181 有兴趣的都可以来了解,分享BAT,阿里面试题、面试经验,讨论技术,裙里资料直接下载就行, 大家一起交流学习!

队列类型对应函数系统默认提供/自定义优先级
主线程串行队列(mian)dispatch_get_main_queue()系统
全局并行队列(global)dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)系统系统提供参数设置
自定义并行队列(Concurrent)dispatch_queue_create("com.sunyazhou.self.queue.concurrent", DISPATCH_QUEUE_CONCURRENT)自定义
自定义串行队列(Serial)dispatch_queue_create("com.sunyazhou.self.queue.serial", DISPATCH_QUEUE_SERIAL)自定义

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 其中的第一个参数就是队列的优先级,具体对于优先级QOS如下:

GCD全局队列优先级宏定义对应枚举数值对应Qos
DISPATCH_QUEUE_PRIORITY_HIGH2QOS_CLASS_USER_INITIATED
DISPATCH_QUEUE_PRIORITY_DEFAULT0QOS_CLASS_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW-2QOS_CLASS_UTILITY
DISPATCH_QUEUE_PRIORITY_BACKGROUNDINT16_MINQOS_CLASS_BACKGROUND

其中dispatch_get_global_queue的第二个参数flag只是一个苹果予保留字段,通常我们传0(你可以试试传1应该队列创建失败)

3.GCD有哪些方法api

  • 队列相关API
  dispatch_get_main_queue(void) //获取主线程队列
  dispatch_get_global_queue(intptr_t identifier, uintptr_t flags) //获取全局队列
  dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr) //创建自定义队列 (一般大家都用域名倒置来区分队列的唯一标识,苹果对标识符是否一致在iOS10后有优化请注意.)
  • 执行API
  dispatch_async(dispatch_queue_t queue, dispatch_block_t block) //在某队列开启异步线程 block{}花括号内的代码将在某队列异步运行
  dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block) //在某队列开启同步线程 block{}花括号内的代码将在某队列同步运行
  dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block) //GCD定时器 多久后执行 block
  dispatch_once(dispatch_once_t *predicate, DISPATCH_NOESCAPE dispatch_block_t block) //单次操作 (单位时间内只允许一个线程进入操作系统的临界区,一般创建单利时使用)这个变量可以区分冷热启动.
  dispatch_apply(size_t iterations, dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue, DISPATCH_NOESCAPE void (^block)(size_t)) //向队列中追加任务操作并等待处理执行结束.
  dispatch_barrier_async()  //将自己的任务插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务插入到队列,然后等待自己的任务结束后才执行后面任务
  dispatch_barrier_sync()  //将自己的任务插入到队列的时候,需要等待自己的任务结束之后才会继续插入被写在它后面的任务,然后执行它们
  • 调度组API
  dispatch_group_create(void) //创建GCD 调度组
  dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue,dispatch_block_t block) //调度组开启异步线程
  dispatch_group_enter() //调度组信号量 需要和leave成对出现.
  dispatch_group_leave() //调度组信号量 需要和enter成对出现.
  dispatch_group_notify() //调度组任务完成通知调用方 操作(一般都回到主线程)
  dispatch_group_wait() //整个调度组 阻塞操作.只等待不做结束处理
  • 信号量API
dispatch_semaphore_create(intptr_t value) //创建信号量 (可以理解为是线程锁)
  dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) //信号-1
  dispatch_semaphore_signal(dispatch_semaphore_t dsema) //信号+1
  • 调度资源API
      dispatch_source_create() 
  dispatch_source_set_timer()
  dispatch_source_set_event_handler()
  dispatch_activate()
  dispatch_resume()
  dispatch_suspend()
  dispatch_source_cancel()
  dispatch_source_testcancel()
  dispatch_source_set_cancel_handler()
  dispatch_notify()
  dispatch_get_context()
  dispatch_set_contex()
  dispatch_queue_set_specific() 给队列设置标识
  dispatch_queue_get_specific() 取出队列标识
  dispatch_get_specific() 查询线程标识
  ...

4.GCD主线程 & 主队列的关系

提交到主队列的任务在主线程执行.

5.如何实现同步,有多少方式就说多少

  • dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block) 在某队列开启同步线程
  • dispatch_barrier_sync() 障碍锁的方式同步
  • dispatch_group_create() + dispatch_group_wait()
  • dispatch_apply() 插队追加 操作同步
  • dispatch_semaphore_create() + dispatch_semaphore_wait() 信号量锁
  • 串行NSOperationQueue队列并发数为1的时候 [NSOpertaion start] 启动任务即使同步操作 (NSOperationQueue.maxConcurrentOperationCount = 1)
  • pthread_mutex底层锁函数
  • 上层应用层封装的NSLock
  • NSRecursiveLock 递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中
  • NSConditionLock & NSCondition 条件锁
  • @synchronized 同步操作 单位时间内只允许一个线程进入临界区
  • dispatch_once() 单位时间内只允许一个线程进入临界区 …

6.dispatch_once实现原理

这个问题问的很傻吊也很高超.因为要解释清楚所有步骤需要记住里面所有代码

我认为这个问题应该从操作系统层面回答, 这个问题的核心是操作系统返回状态决定的,单位时间内操作系统只允许一个线程进入临界区,进入临界区的线程会被标记

回归到代码就是

dispatch_once(dispatch_once_t *val, dispatch_block_t block)  
	|_____dispatch_once_f(val, block, _dispatch_Block_invoke(block))  
		|_______&l->dgo_once  // &l->dgo_once 地址中存储的值。显然若该值为DLOCK_ONCE_DONE,即为once已经执行过

dgo_once是dispatch_once_gate_s的成员变量

typedef struct dispatch_once_gate_s {
	union {
		dispatch_gate_s dgo_gate;
		uintptr_t dgo_once;
	};
} dispatch_once_gate_s, *dispatch_once_gate_t;

有个内联函数static inline bool _dispatch_once_gate_tryenter(dispatch_once_gate_t l)

这个内联函数返回一个 原子性操作的结果

return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,(uintptr_t)_dispatch_lock_value_for_self(), relaxed)

比较+交换 的原子操作。比较 &l->dgo_once 的值是否等于 DLOCK_ONCE_UNLOCKED

这样就实现了我们的执行1次的GCD API.

dispatch_once的底层实现

7.什么情况下会死锁

造成死锁的主要是 线程信息不对称,出现A等B的同时 B也在等A的情况.

/// 在主线程中执行这句代码
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"这里死锁了");
});

主线程一直不会执行完,追加到主线程同步执行的任务显然惨死.卡住主线程无法自拔.

其它的情况 都是资源产生竞争或者调用lock的函数没有调用unlock导致,异步线程 先后调用等产生的较多.

8.有哪些类型的线程锁,分别介绍下作用和使用场景

锁类型使用场景备注
pthread_mutex互斥锁PTHREAD_MUTEX_NORMAL,#import <pthread.h>
OSSpinLock自旋锁不安全,iOS 10 已启用
os_unfair_lock互斥锁替代 OSSpinLock
pthread_mutex(recursive)递归锁PTHREAD_MUTEX_RECURSIVE,#import <pthread.h>
pthread_cond_t条件锁#import <pthread.h>
pthread_rwlock读写锁读操作重入,写操作互斥
@synchronized互斥锁性能差,且无法锁住内存地址更改的对象
NSLock互斥锁封装 pthread_mutex
NSRecursiveLock递归锁封装pthread_mutex(recursive)
NSCondition条件锁封装 pthread_cond_t
NSConditionLock条件锁可以指定具体条件值 封装 pthread_cond_t

9.NSOperationQueue中的maxConcurrentOperationCount默认值

默认值 -1. 这个值操作系统会根据资源使用的综合开销情况设置.

10.NSTimer、CADisplayLink、dispatch_source_t` 的优劣

定时器类型优势劣势
NSTimer使用简单依赖 Runloop,具体表现在 无 Runloop 无法使用、NSRunLoopCommonModes、不精确
CADisplayLink依赖屏幕刷新频率出发事件,最精.最合适做UI刷新若屏幕刷新被影响,事件也被影响、事件触发的时间间隔只能是屏幕刷新 duration 的倍数、若事件所需时间大于触发事件,跳过数次、不能被继承
dispatch_source_t不依赖 Runloop依赖线程队列,使用麻烦 使用不当容易Crash

总结

今天这篇多线程 也算是一个objc开发者的知识总结,这里面问到的知识大部分和队列线程关系比较多. 高阶一些的搞法并没有. 比如:如何停掉dispatch_source_t的定时器.再比如 为什么要存在dispatch_source. 下一篇我们讲解一下 视图&图像相关文章.

文章链接:https://www.sunyazhou.com/2020/09/GCD/

标签:面试题,队列,iOS,dispatch,queue,线程,多线程,block,once
来源: https://blog.csdn.net/henry_lei/article/details/110818940

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有