iOS面试题(一) – 22道多线程面试题

匿名网友 匿名网友 发布于: 2021-05-29 00:00:00
阅读 319 收藏 0 点赞 0 评论 0

## 1.`NSThread`相关知识

 

## 2.`GCD` 相关知识?(栅栏函数、Group、定时器、信号量、队列类型、任务派发方式、快速迭代、延迟处理)

 

##### 1.栅栏函数(控制任务的执行顺序)

 

“`objc

    dispatch_barrier_async(queue, ^{

    

        NSLog(@”barrier”);

    });

“`

 

#####     2.延迟执行(延迟·控制在哪个线程执行)

 

“`objc

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSLog(@”—%@”,[NSThread currentThread]);

    });

“`

 

#####     3.一次性代码

 

“`objc

   static dispatch_once_t onceToken;

   dispatch_once(&onceToken, ^{

 

       NSLog(@”—–“);

   });

“`

 

#####     4.快速迭代(开多个线程并发完成迭代操作)

 

“`objc

    dispatch_apply(subpaths.count, queue, ^(size_t index) {

    });

“`

 

 

#####     5.队列组(同栅栏函数)

 

“`objc

    dispatch_group_t group = dispatch_group_create();

    // 队列组中的任务执行完毕之后,执行该函数

    dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);

 

    // 进入群组和离开群组

    dispatch_group_enter(group);//执行该函数后,后面异步执行的block会被gruop监听

    dispatch_group_leave(group);//异步block中,所有的任务都执行完毕,最后离开群组

    //注意:dispatch_group_enter|dispatch_group_leave必须成对使用

“`

#####     6.信号量(并发编程中很有用)

 

## 3.`NSOperation` 和 `NSOperationQueue`相关知识?

 

(最大并发数、线程依赖)

 

## 4.如何实现线性编程?

 

信号量、栅栏、Dispatch_group

 

## 5.说一下 `GCD` 并发队列实现机制?

 

利用的时间片轮转

 

## 6.`NSLock`

 

> `NSLock` 遵循 `NSLocking` 协议,`lock` 方法是加锁,`unlock` 是解锁,`tryLock` 是尝试加锁,如果失败的话返回 NO,`lockBeforeDate:` 是在指定 `Date` 之前尝试加锁,如果在指定时间之前都不能加锁,则返回 `NO`。

 

“`objc

@protocol NSLocking

 

– (void)lock;

– (void)unlock;

 

@end

 

@interface NSLock : NSObject <NSLocking> {

@private

    void *_priv;

}

 

– (BOOL)tryLock;

– (BOOL)lockBeforeDate:(NSDate *)limit;

 

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

 

@end

“`

 

## 7.`NSContion`

 

“`objc

@interface NSCondition : NSObject <NSLocking> {

@private

    void *_priv;

}

 

– (void)wait;

– (BOOL)waitUntilDate:(NSDate *)limit;

– (void)signal;

– (void)broadcast;

 

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

 

@end

 

“`

 

`NSCondition` 的对象实际上作为一个锁和一个线程检查器,锁上之后其它线程也能上锁,而之后可以根据条件决定是否继续运行线程,即线程是否要进入 `waiting` 状态,经测试,直接进入 `waiting` 状态,当其它线程中的该锁执行 `signal` 或者 `broadcast` 方法时,线程被唤醒,继续运行之后的方法。

 

 

## 8.条件锁 – `NSContionLock` 

 

“`objc

@interface NSConditionLock : NSObject <NSLocking> {

@private

    void *_priv;

}

 

– (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

 

@property (readonly) NSInteger condition;

– (void)lockWhenCondition:(NSInteger)condition;

– (BOOL)tryLock;

– (BOOL)tryLockWhenCondition:(NSInteger)condition;

– (void)unlockWithCondition:(NSInteger)condition;

– (BOOL)lockBeforeDate:(NSDate *)limit;

– (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

 

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

 

@end

“`

`NSConditionLock` 和 `NSLock` 类似,都遵循 `NSLocking` 协议,方法都类似,只是多了一个 `condition` 属性,以及每个操作都多了一个关于 `condition` 属性的方法,例如 `tryLock`,`tryLockWhenCondition:`,`NSConditionLock` 可以称为条件锁,只有 `condition` 参数与初始化时候的 `condition` 相等,`lock` 才能正确进行加锁操作。而 `unlockWithCondition:` 并不是当 `Condition` 符合条件时才解锁,而是解锁之后,修改 `Condition` 的值。

 

## 9.递归锁 – `NSRecursiveLock`

 

“`objc

@interface NSRecursiveLock : NSObject <NSLocking> {

@private

    void *_priv;

}

 

– (BOOL)tryLock;

– (BOOL)lockBeforeDate:(NSDate *)limit;

 

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

 

@end

NSRecursiveLock 是递归锁,他和 NSLock 的区别在于,NSRecursiveLock 可以在一个线程中重复加锁(反正单线程内任务是按顺序执行的,不会出现资源竞争问题),NSRecursiveLock 会记录上锁和解锁的次数,当二者平衡的时候,才会释放锁,其它线程才可以上锁成功。

“`

 

## 10.同步锁 – `Synchronized(self) {// code}`

 

 

`@synchronized(object)` 指令使用的 `object` 为该锁的唯一标识,只有当标识相同时,才满足互斥。

 

 

`@synchronized(object)` 简化了加锁的行为,我们不在需要显示的加锁。

 

##11.信号量 – `dispatch_semaphore`。

 

> 如果获取不到 `锁` ,会将当前线程阻塞、休眠,直到其他线程释放 `锁` 时,会唤醒当前线程。

 

## 12.自旋锁 – `OSSpinLock`

 

> 自旋锁是一种 “忙等” 的锁,它适用于轻量访问,譬如在 `引用计数表` 和 原子性`atomic`。

 

如果当前线程的 `锁` 被其他线程获取,当前线程会不断探测 `锁` 是否有被释放,如果检测出释放,会第一时间获取这个锁

 

## 13.互斥锁 – `pthread_mutex`

 

 

 

“`objc

 

// 初始化方法()

 

int pthread_mutex_init(pthread_mutex_t * __restrict, const pthread_mutexattr_t * __restrict);

 

 

// pthread_mutex_t * __restrict 代表互斥锁的类型,有以下四种

1.PTHREAD_MUTEX_NORMAL 缺省类型,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。

2.PTHREAD_MUTEX_ERRORCHECK 检错锁,如果同一个线程请求同一个锁,则返回 EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。

3.PTHREAD_MUTEX_RECURSIVE 递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。

4.PTHREAD_MUTEX_DEFAULT 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。

“`

 

“`objc

// 常用的一些方法

int pthread_mutex_lock(pthread_mutex_t *);

int pthread_mutex_trylock(pthread_mutex_t *);

int pthread_mutex_unlock(pthread_mutex_t *);

int pthread_mutex_destroy(pthread_mutex_t *);

int pthread_mutex_setprioceiling(pthread_mutex_t * __restrict, int,int * __restrict);

int pthread_mutex_getprioceiling(const pthread_mutex_t * __restrict,int * __restrict);

“`

 

 

###### 如何利用 `pthread_mutex` 创建一个递归锁

 

 

“`objc

static pthread_mutex_t theLock;

pthread_mutexattr_t attr;

pthread_mutexattr_init(&attr);

pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

pthread_mutex_init(&theLock, &attr);

“`

 

## 14.分步锁 – `NSDistributedLock`。

 

在 引用计数表的数据结构里,一张 `sideTable` 表利用 `分离锁` 被分成了多个部分。

 

这样可以对一张表的多个部分,同时进行操作,提升了效率。

 

## 15.如何确保线程安全?

 

* 采用 `串行队列`。

* 加上 `同步锁`。

 

 

## 16.`NSMutableArray`、和 `NSMutableDictionary`是线程安全的吗?`NSCache`呢?

 

> 在做缓存时,优先使用 `NSCache` 而不是 `NSDictionary` ,我们熟悉的框架 `SDWebimage` 就是采用的 `NSCache`。

 

`NSCache` 优点如下:

 

1. 系统资源将要耗尽时,它可以自动删减缓存。

2. 可以设置最大缓存数量。

3. 可以设置最大占用内存值。

4. `NSCache` 线程是安全的。

 

## 17.多线程的 `并行` 和 `并发` 有什么区别?

 

并行:充分利用计算机的多核,在多个线程上同步进行

并发:在一条线程上通过快速切换,让人感觉在同步进行

 

## 18.多线程有哪些优缺点?

 

 

##### 创建线程是需要花费资源的

 

– 一条主线程占用1M,一条子线程占用 512Kb。

– 线程的切换也是需要花费资源的

– 优点就是提升效率,充分利用了计算机的多核特性。

 

## 19.如何自定义 `NSOperation` ?

 

 

需要重写其 `main` 方法

 

## 20.`GCD` 与 `NSOperationQueue` 有哪些异同?

 

 

 

* 1> `GCD` 是纯 `C` 语言的 `API` 。`NSOperationQueue` 是基于 `GCD` 的 `OC` 的封装。

* 2> `GCD` 只支持 `FIFO` 队列,`NSOperationQueue` 可以方便设置执行顺序,设置最大的并发数量。

* 3> `NSOperationQueue` 可是方便的设置 `operation` 之间的依赖关系,`GCD` 则需要很多代码。

* 4> `NSOperationQueue` 支持 `KVO`,可以检测 `operation` 是否正在执行`(isExecuted)`,是否结束`(isFinished)`,是否取消`(isCanceled)`

* 5>GCD的执行速度比NSOperationQueue快。

 

 

 

#### 使用场合:

 

* 任务之间不太相互依赖:`GCD`

 

* 任务之间有依赖或要监听任务的执行情况:`NSOperationQueue`

 

## 21.解释一下多线程中的死锁?

 

 

 

死锁是由于多个线程(进程)在执行过程中,因为争夺资源而造成的互相等待现象,你可以理解为卡主了。产生死锁的必要条件有四个:

 

– 互斥条件 : 指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

– 请求和保持条件 : 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

– 不可剥夺条件 : 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

– 环路等待条件 : 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

 

 

“`objc

dispatch_sync(dispatch_get_main_queue(), ^{

        NSLog(@”2″);

    });

    

NSLog(@”1″);

// 什么也不会打印,直接报错

“`

 

 

最常见的就是 `同步函数` + `主队列` 的组合,本质是队列阻塞。

 

### 22.分类和类拓展的区别?

 

* 1.`分类` 的加载在 `运行时`,`类拓展` 的加载在 `编译时`。

 

* 2.`分类` 不能给系统的类添加方法。

* 3.`类拓展` 只以声明的形式存在,一般存在 .m 文件中。

 

******

持续更新中~

******

 

## **文末推荐:iOS热门文集**

 

*   **① [BAT等各个大厂iOS面试真题+答案大全](https://github.com/iOS-Mayday/heji)**

 

*   **② [iOS中高级开发必看的热门书籍(经典必看)](https://github.com/iOS-Mayday/iOSAdvanced-Roadmap)**

 

*   **③ [iOS开发高级面试”简历制作”指导](https://github.com/iOS-Mayday/Interview-questions-iOS)**

*   **④ [iOS面试流程到基础知识大全](https://github.com/iOS-Mayday/iOS-Interview-Strategy)**

 

 

 

 

 

 

 

 

 

评论列表
文章目录