多线程及相关面试题与拓展

首先,抛出几个面试题,相信看完介绍就会明白怎么解答了。

  • 你理解的多线程?
  • iOS的多线程方案有哪几种?你更倾向于哪一种?
  • GCD 的队列类型
  • 线程安全的处理手段有哪些?
  • OC你了解的锁有哪些?

ios中常见的多线程方案

我们这里主要讲解GCD

GCD中有两个常用的函数,分别为同步和异步

// 同步
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务

// 异步
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

 队列也分为两大类型:并发队列和串行队列

并发队列:

  • 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
  • 并发功能只有在异步(dispatch_async)函数下才有效

串行队列:让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

这里同步、异步、并发、串行4个术语容易产生混淆,同步和异步主要影响能不能开启新的线程,并发和串行主要影响任务的执行方式

同步:在当前线程中执行任务,不具备开启新线程的能力

异步:在新的线程中执行任务,具备开启新线程的能力

并发:多个任务并发(同时)执行

串行:一个任务执行完毕后,再执行下一个任务

这里有个注意:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁),比如

- (void)interview01
{
    // 问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"2");
    });
    NSLog(@"3");
    
    // dispatch_sync立马在当前线程同步执行任务
}
打印台:死锁,卡在dispatch_sync(queue, ^{,原因是,同步执行会在执行完1后,直接执行任务2,但是主线程本就是串行,要等执行完interview01这个任务后,再去执行其他的,你等我,我等你,死锁

- (void)interview02
{
    // 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
    });
    NSLog(@"执行任务3");
    // dispatch_async不要求立马在当前线程同步执行任务
}
打印台: 1、3、2  
- (void)interview03
{
    // 问题:以下代码是在主线程执行的
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
}
打印台: 1、5、2 然后卡在dispatch_sync(queue, ^{ // 1  原因:因为异步,所以正常执行1 5 2,到那时在3的时候,因为同步,要直接执行3,但是因为串行队列,4也要直接执行,所以你等我,我等你,死锁
- (void)interview04
{
    // 问题:以下代码是在主线程执行的
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        dispatch_sync(queue2, ^{ // 1
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
}
打印台:1、5、2、3、4

- (void)interview05
{
    // 问题:以下代码是在主线程执行的
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
}
打印台:1、5、2、3、4

多线程的安全隐患

多个线程可能会访问同一块资源,很容易引发数据错乱和数据安全问题

解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行) 常见的线程同步技术是:加锁

iOS中常见的线程同步有:OSSpinLock 、 os_unfair_lock 、 pthread_mutex、 dispatch_semaphore 、dispatch_queue(DISPATCH_QUEUE_SERIAL) 、NSLock 、NSRecursiveLock 、NSCondition 、NSConditionLock 和@synchronized

OSSpinLock

OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源

目前已经不再安全,可能会出现优先级反转问题

如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁

需要导入头文件#import <libkern/OSAtomic.h>

os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock ,

从iOS10开始才支持 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等

需要导入头文件#import <os/lock.h>

pthread_mutex

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态

需要导入头文件#import <pthread.h>

还有个递归锁,只是初始化锁的时候的属性改变

条件锁:初始化一样,只是在需要的地方进行休眠,然后在其他地方进行广播,会发送单个信号通知,唤醒

NSLock、NSRecursiveLock

NSLock是对mutex普通锁的封装,

NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致

NSCondition

NSCondition是对mutex和cond的封装

NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值

dispatch_semaphore

semaphore叫做”信号量”, 信号量的初始值,可以用来控制线程并发访问的最大数量 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

dispatch_queue

直接使用GCD的串行队列,也是可以实现线程同步的

@synchronized

@synchronized是对mutex递归锁的封装

源码查看:objc4中的objc-sync.mm文件

@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作

iOS线程同步方案性能比较

性能从高到低排序:os_unfair_lock、 OSSpinLock、 dispatch_semaphore 、pthread_mutex 、dispatch_queue(DISPATCH_QUEUE_SERIAL)、 NSLock 、NSCondition 、pthread_mutex(recursive)、 NSRecursiveLock 、NSConditionLock 、@synchronized

什么情况使用自旋锁比较划算?

  • 预计线程等待锁的时间很短
  • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
  • CPU资源不紧张
  • 多核处理器

什么情况使用互斥锁比较划算?

  • 预计线程等待锁的时间较长
  • 单核处理器
  • 临界区有IO操作
  • 临界区代码复杂或者循环量大
  • 临界区竞争非常激烈

这里还有一个属性就是atomic,我们平常都是用的nonatomic,atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值