iOS面试-多线程

计算机多进程发展的背景:

早期计算机工作是单道批处理程序处理,讲写好的作业卡依次放进计算机的批处理程序执行,效率非常低,后面出现了多道批处理程序,同一段时间允许多个程序同事快速执行,虽然效率很高,但是后面随着硬件技术的跟进,除了分时系统处理作业这是已经成为是进程作业;根据每个进程的时间分片,同一时间里快速切换执行。后面又出现实时系统,更加高效。。。

多线程概念:

多线程是指在软件或者硬件条件下可以实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件的支持可以在同一时间执行多个线程,进而提高计算机的处理能力。

多线程于进程的关系:

进程是程序执行的基本单元,而线程是程序执行的基本单位,一个进程可以拥有多个线程,并至少拥有一个线程,每个线程独立执行,多个线程可以共享它所在进程的代码、数据、进程空间和对应的堆空间;

多线程对程序开发带来的便利:

  1. 单线程在遇到耗时操作的时候会阻塞UI线程,用户界面的鼠标点击、手势滚动都会立刻进行执行,造成了很不好的用户体验,多线程技术允许将耗时操作放进单独的线程里区执行,从而不会影响UI线程的交互,提高用户体验;
  2. 相对进程来说,多线程的创建、切换所需要cpu消耗、内存占用量都会很少;
  3. 对于多核的操作系统,单一线程无法很好使用的多核资源,造成资源浪费。

多线程引进的问题:

  1. 实现多线程的代码会更加复杂、难度、维护起来难;
  2. 对于模型共享数据来时是多个线程共享的,可能会出现线程死锁的问题;
  3. 由于线程的创建、调度、切换都会消耗一定的cpu计算资源、内存资源、指令调度资源,疯狂的创建多线程会起到适得其反的后果;
  4. 数据不安全,对于同一个共享区域的数据,可能会被篡改;

iOS开发过程中多线程实现的方案:

基于c语言的POSIX线程,成为pthread 线程;是操作系统的多线程实现方案,由于oc中可以实现也可以使用c混编,posix线程在iOS开发中也可以使用;posix线程的优点是较为底层性能会高效,但是由于c语言开发,所以需要我们手动的管理线程的内容,对于开发效率是一种不利;

nsthread 线程基于c语言封装、面向对象的多线程实现方案,api如下:

1.  init 方法实现多线程创建,该方法需要我们手动执行strart指令,将它放进带线程就绪队列,等待系统自己出发;
2. detachNewThreadSelector 方法创建并自动放到线程就绪队列等待系统自动去出发;

2.1 、 nsthread 优点:

1. nsthread提供适合oc开发人员使用多线程实现api,降低开发门槛;
2. nsthread 提供了一套工具酷,我们可以快捷拿到多线程的信息和状态,例如: 是否是主线程、是否被取消,是否正在运行等

2.2 、nsthread 缺点

1. nsthread 的最大缺点是线程之间的通信比较麻烦,也是设计的一种缺陷;

GCD

  1. 概念

gcd 我们称为中央调度管理,基于c接口,是apple公司为了线程不能充分利用多核资源引入的一个多线程实现方案;它的工作流程是将一个事件分成若干小任务放入串行或者并发执行队列,遵循先进先出的规则去加入线程执行单元,然后执行其同步或者异步的执行;
1.1 : 优点:

高效: 基于mach内核线程调度;
线程安全:不用开发者关心线程的内存问题;
使用简单,少代码解决大问题;
  1. 核心机制-队列

gcd 的核心内容是队列
gcd 的队列分为: 串行、并发、主队列、全局队列、组队列

  1. 串行/并发/同步异步 组合方式对应的结果
 1. 主线程的时间优先
 2. 串行同步执行: 执行完一个任务,再执行下一个任务。不开启新线程,都在主线程执行。
 3. 串行异步执行: 开启新线程,但因为任务是串行的,所以还是按顺序执行任务。
 4. 并发队列异步执行: 并发执行,会开启新的线程。
 5. 并发队列同步执行; 按顺序执行,不会开启新的线程。
 6. 主队列同步事件: 
 7. 主队列异步事件:  按次序执行。
  1. gcd 的特别函数:
    4.1 栏栅函数 dispatch_barrier_(a)sync

当我们需要异步事件同步去执行的时候我们需要用函数 dispatch_barrier_sync
dispatch_barrier_async 异步执行

4.2 信号量

dispatch_semaphore 为了解决共享资源竞争问题,可以用信号量来决定资源是否可以被访问。
dispatch_semphore_create 创建一个信号量,参数是最大可以同事访问的线程;
dispatch_semphore_waite 对信号量执行减1操作,如果结果大于0,就可以直接进行资源访问;
dispatch_semphone_sign 对信号变量执行加一操作,访问完成就要释放对资源的占用权利,即信号量加1,不影响其他线程访问。

4.3 延迟执行函数

扫描二维码关注公众号,回复: 6600962 查看本文章

dispatch_after_t 函数 参数: dispatch_time_t dispatch_queue_t block

4.4 组变量
当我们需要多个请求后才能进入下一步时,我们需要下面的函数\

dispatch_group_t

1. 创建一个全局的队列,默认优先级
2. 创建一个group_t 
3. 创建group任务加入到全局队列
4. 监听组队列完成的notify dispatch_group_notify

// 加载图片例子:
   NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        sleep(4);
        NSLog(@"子线程1-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        sleep(3);
        NSLog(@"子线程2-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部结束-----%@", [NSThread currentThread]);
    });

4.5 dispatch_group_enter/dispatch_group_leave 函数

4.4的任务是组任务力里只是简单的输出一句话,如果在里面加入一个异步请求就出出现问题:

有问题的代码:

NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(4);
            NSLog(@"模拟请求1-----%@", [NSThread currentThread]);
        });
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(3);
            NSLog(@"模拟请求2-----%@", [NSThread currentThread]);
        });
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部结束-----%@", [NSThread currentThread]);
    });

它打印log: 全部开始-全部结束-任务2,任务1, 我们发现这样的话与我们的要求的不一样,我们要求的是所有的任务都结束才会执行notify 函数但是它提前执行啦。

为了解决上面的问题需要用到 dispatch_group_enter/dispatch_group_leave
在组组任务内部,每次进行异步操作之前,先执行 dispatch_group_enter函数,参数是group变量; 当内部的任务完成时执行dispatch_group_leave()
代码如下:

NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(4);
            NSLog(@"模拟请求1-----%@", [NSThread currentThread]);
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(3);
            NSLog(@"模拟请求2-----%@", [NSThread currentThread]);
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部结束-----%@", [NSThread currentThread]);
    });

4.6 dispatch_apply 快苏迭代函数 参数: 迭代的次数,所在的队列,任务

4.7 dispatch_once : 只执行一次任务,通常用在单例,

5.gcd的常见问题
5.1 线程死锁问题
原因:队列引起- 场景:在主线程里执行同步将任务添加到主队列里
例子: 加入在viewdidload 方法中 我们同步执行将block(执行一句日志)添加祝队列,在这易操作后面加上一个结束日志,运行发现,block的日志、结束日志都没有打印,通过现象说明是block来不及执行、结束日志也无法执行。原因是,首先程序首先将同步执行函数加入到线程执行单元里,然后根据同步执行函数要求将block加入到执行主队列里,由于是同步函数,只能等到block结束,同步函数才能返回,而block在主队列,它的机制是只有上一个出队列的任务完成它才可以出队列,这样同步函数不能返回导致结束就无法按顺序打印,block无法执行,导致同步函数无法返回,系统无法进入下一步,就发错-死锁。

猜你喜欢

转载自blog.csdn.net/weixin_33782386/article/details/90922048
今日推荐