iOS面试之多线程模块

多线程

多线程内容如下:

  • GCD
  • NSOperation
  • NSThread
  • 多线程与锁
多线程.png

1.GCD

- 同步/异步 和 串行/并发
- dispatch_barrier_async
- dispatch_group
  • 同步/异步和串行/并发
//同步分派一个任务到串行队列
- dispatch_sync(serial_queue,^{//任务});
//异步废牌一个任务到串行队列
- dispatch_async(serial_queue,^{//任务});
//同步分派一个任务到并发队列
- dispatch_sync(concurrent_queue,^{//任务})
//异步分派一个任务到并发队列
- dispatch_async(concurrent_queue,^{//任务})
  • 同步串行
-(void)viewDidload{
     dispatch_sync(dispatch_get_main_queue(),^{
         [self doSomething];
     });
}
//产生死锁
原因:队列引起的循环等待
iOS面试之多线程模块_第1张图片
队列引起的循环等待.png
- 主队类先提交一个viewDidLoad,接着提交一个Block
- 两个任务都要提交到主线程去执行
- 我们分派viewDidLoad到主线程处理,需要调用Block,当Block同步调用完成之后,
viewDidLoad方法才可以继续向下执行
- viewDidLoad方法的调用结束,需要依赖于后续提交的Block任务
- Block想执行,需要依赖主队列先进先出的性质,需要等待ViewDidLoad的完成
- Block想要处理,依赖于ViewDidLoad的完成
- 所以,形成了相互等待
-(void)viewDidload{
     dispatch_sync(serialQueue,^{
         [self doSomething];
     });
}
//没问题
iOS面试之多线程模块_第2张图片
同步串行.png
- viewDidLoad方法提交到主队列当中,会运行处理到主线程中
- viewDidLoad执行到某一时刻,需要同步提交一个任务到串行队列
- 串行队列其实是同步方式提交的,在当前线程执行,最终在主线程执行
- 串行队列任务,在主线程提交完成后,再到主队类完成viewdidload其他任务
  • 同步并发
-(void)viewDidLoad{
           NSLog(@"1");
           dispatch_sync(global_queue,^{
                 NSLog(@"2");
                 dispatch_sync(global_queue,^{
                         NSLog(@"3");
                 });
                 NSLog(@"4");
           });
           NSLog(@"5");
}

//12345
//同步分派任务在当前线程中执行
  • 异步串行
-(void)viewDidload{
     dispatch_async(dispatch_get_main_queue(),^{
         [self doSomething];
     });
}
  • 异步并发
-(void)viewDidload{
     dispatch_async(global_queue,^{
         NSLog(@"1");
         [self performSelector:@selector(logPrint)] withObject: nil afterDelay:0];
         NSLog(@"3");
     });
}
-(void)logPrint{
      NSLog(@"2");
}

//13
- 我们通过异步方式分派到全局并发队列中,本身block,会在GCD维护的线程池当中进行执行,处理.GCD默认并没有开启runloop
- performSelector: withObject: afterDelay: 延迟0秒提交Selector任务,需要相应创建提交runloop任务的逻辑的
- GCD创建的线程没有runloop的情况下, performSelector方法就会失效
  • dispathc_barrier_async()
    如何使用GCD实现多读单写?
iOS面试之多线程模块_第3张图片
读写数据.png
- 读者,读者并发(实现多读)
- 读者,写着互斥(有读取数据的时候,不能存在写者写数据)
- 写着,写着互斥(有一个写线程在写数据,另一个写线程就不能写数据)
iOS面试之多线程模块_第4张图片
多读单写.png
dispathc_barrier_async(concurrent_queue,^{//写操作});
#import "User.h"

@interface User()
{
    // 定义一个并发队列
    dispatch_queue_t concurrent_queue;
    // 用户数据中心, 可能多个线程需要数据访问
    NSMutableDictionary *userDic;
}

@end
// 多读单写模型
@implementation User

- (id)init
{
    self = [super init];
    if (self) {
        // 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
        concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        // 创建数据容器
        userDic = [NSMutableDictionary dictionary];
    }
    
    return self;
}

- (id)objectForKey:(NSString *)key
{
    __block id obj;
    // 同步读取指定数据
    dispatch_sync(concurrent_queue, ^{
        obj = [userDic objectForKey:key];
    });
    return obj;
}

- (void)setObject:(id)obj forKey:(NSString *)key
{
    // 异步调用设置数据
    dispatch_barrier_async(concurrent_queue, ^{
        [userDic setObject:obj forKey:key];
    });
}
  • Dispatch_group_async()
使用GCD实现:A,B,C三个任务并发,完成后执行任务D
iOS面试之多线程模块_第5张图片
Dispatch_group_async.png
#import "Group.h"

@interface Group()
{
    dispatch_queue_t concurrent_queue;
    NSMutableArray  *arrayURLs;
}

@end

@implementation Group

- (id)init
{
    self = [super init];
    if (self) {
        // 创建并发队列
        concurrent_queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
        arrayURLs = [NSMutableArray array];
    }
    return self;
}

- (void)handle
{
    // 创建一个group
    dispatch_group_t group = dispatch_group_create();
    
    // for循环遍历各个元素执行操作
    for (NSURL *url in arrayURLs) {
        // 异步组分派到并发队列当中
        dispatch_group_async(group, concurrent_queue, ^{
            //根据url去下载图片
            NSLog(@"url is %@", url);
        });
    }
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 当添加到组中的所有任务执行完成之后会调用该Block
        NSLog(@"全部下载完成");
    });
}
@end

2.NSOperation

需要与NSOperationQueue配合使用来实现多线程方案
优势特点:
- 添加任务依赖
- 任务执行状态控制
- 最大并发量
  • 任务执行状态控制
状态有哪些?
- isReady 当前任务的就绪状态
- isExecuting 当前任务是否正在执行中
- isFinished 当前任务是否已经执行完成
- isCancelled 当前任务是否已经取消
状态控制
如果只重写main方法,底层控制变更任务执行完成状态,以及任务退出
如果重写了start方法,自行控制任务状态
系统是怎样移除一个isFinished=YES的NSOperation的?
通过KVO

3.NSThread

iOS面试之多线程模块_第6张图片
NSThread启动流程.png
start()
|
main()
|
perfomSelector
|
exit(结束线程)

4.多线程与锁

在日常开发中,都使用过哪些锁?
- @synchronized
- atomic
- OSSpinLock
- NSRecursiveLock
- NSLock
- dispatch_semaphore_t
  • @synchronized
- 一般在创建单例对象的时候使用
- 多线程环境下创建线程是唯一的
  • atomic
- 修饰属性的关键字
- 对被修饰对象进行原子操作(不负责使用)

@property(atomic) NSMutableArray *array;
self.array = [NSMutableArray array];//这样保证线程的安全性
[self.array addObject:obj];//不能保证线程安全的
  • OSSpinLock
- 自旋锁
- 循环等待访问,不释放当前资源
- 用于轻量级数据访问(引用计数+1/-1操作)
  • NSLock
-(void)A{
    [lock lock];
    [self B];
    [lock unlock];
}

-(void)B{
   [lock lock];
   //操作逻辑
   [lock unlock];
}
//导致死锁
- 使用NSLock 对临界区加锁处理的时候,当前某个线程调用lock之后,获取得到锁
- 到B方法后,同一把锁友获取了一次,导致了死锁

//解决方案
通过递归锁NSRecursiveLock
  • NSRecursiveLock
-(void)A{
    [recursiveLock lock];
    [self B];
    [recursiveLock unlock];
}

-(void)B{
   [recursiveLock lock];
   //操作逻辑
   [recursiveLock unlock];
}

递归锁的特点就是重入
  • dispatch_semaphore_t
- 信号量
- dispatch_semaphore_create(1);
- dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
- dispatch_semaphore_signal(semaphore);

面试题:

  • 怎样用GCD实现多读单写?
  • iOS系统为我们提供几种多线程技术各自特点是怎样的?
  • NSOperation对象在isFinished之后是怎样从queue当中移除掉的?
  • 用过哪些锁?你是怎样使用的?

QQ交流群: 796142709

你可能感兴趣的:(iOS面试之多线程模块)