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

2,029 阅读7分钟

1.NSThread相关知识

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

1.栅栏函数(控制任务的执行顺序)
    dispatch_barrier_async(queue, ^{
    
        NSLog(@"barrier");
    });
2.延迟执行(延迟·控制在哪个线程执行)
    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.一次性代码
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{

       NSLog(@"-----");
   });
4.快速迭代(开多个线程并发完成迭代操作)
    dispatch_apply(subpaths.count, queue, ^(size_t index) {
    });
5.队列组(同栅栏函数)
    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.NSOperationNSOperationQueue相关知识?

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

4.如何实现线性编程?

信号量、栅栏、Dispatch_group

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

利用的时间片轮转

6.NSLock

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

@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

@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

@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

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

9.递归锁 - NSRecursiveLock

@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


// 初始化方法()

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 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。
// 常用的一些方法
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 创建一个递归锁
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.GCDNSOperationQueue 有哪些异同?

  • 1> GCD 是纯 C 语言的 APINSOperationQueue 是基于 GCDOC 的封装。
  • 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占用的资源。
dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    
NSLog(@"1");
// 什么也不会打印,直接报错

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

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

  • 1.分类 的加载在 运行时类拓展 的加载在 编译时

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

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


持续更新中~


文末推荐:iOS热门文集