洋仔的博客 洋仔的博客
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

洋仔

奋斗的小青年
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Nodejs

  • 博客搭建

  • iOS基础知识

    • iOS底层相关

    • Runloop系列

    • Runtime系列

    • 内存管理系列

    • Block系列

    • 线程系列

      • 线程系列
        • GCD执行原理?
        • GCD和NSOperation的比较
        • GCD中的队列
          • 主队列添加同步任务
          • 主队列添加异步任务
          • 并发队列添加异步任务
          • 并发队列添加同步任务
          • 串行队列添加同步任务
          • 串行队列添加异步任务
          • 串行队列添加同步和异步混合
      • 线程系列-锁
      • 进程系列
      • dispatch_once的实现原理
    • KVC跟KVO系列以及通知中心

    • UI系列

    • 离屏渲染系列

    • 组件化系列跟架构

    • OC跟webview交互系列

    • 持久化系列

    • APP编译系列

    • APP性能优化系列

    • cocoapods系列

    • swift系列

    • Git系列

    • 网络相关

    • 三方库系列

    • 系统原理

    • 总结系列

    • 算法系列

    • 数据结构系列

  • 前端

  • 技术
  • iOS基础知识
  • 线程系列
洋仔
2023-08-11
目录

线程系列

# 线程

# 什么是多线程?

多线程是指实现多个线程并发执行的技术,进而提升整体处理性能。

同一时间,CPU 只能处理一条线程,多线程并发执行,其实是 CPU 快速的在多条线程之间调度(切换)如果 CPU 调度线程的时间足够快, 就造成了多线程并发执行的假象

  • 主线程的栈区 空间大小为1M,非常非常宝贵
  • 子线程的栈区 空间大小为512K内存空间

优势

充分发挥多核处理器的优势,将不同线程任务分配给不同的处理器,真正进入“并行计算”状态

弊端 新线程会消耗内存控件和cpu时间,线程太多会降低系统运行性能。

# 进程与线程之间的区别

  • 进程:正在运行的程序,负责程序的内存分配,每一个进程都有自己独立的虚拟内存空间。(一个程序运行的动态过程)
  • 线程:线程是进程中一个独立执行的路径(控制单元)一个进程至少包含一条线程,即主线程可以将耗时的执行路径(如网络请求)放在其他线程中执行。

# 进程和线程的比较

  • 线程是 CPU 调用的最小单位
  • 进程是 CPU 分配资源和调度的单位
  • 一个程序可以对应多个进程,一个进程中可有多个线程,但至少要有一条线程, 同一个进程内的线程共享进程资源

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

并行:充分利用计算机的多核,在多个线程上同步进行 并发: 所谓并发,就是通过一种算法将 CPU 资源合理地分配给多个任务,当一个任务执行 I/O 操作时,CPU 可以转而执行其它的任务,等到 I/O 操作完成以后,或者新的任务遇到 I/O 操作时,CPU 再回到原来的任务继续执行。

# GCD系列

# GCD执行原理?

GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?) 而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。

如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。

如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。

这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开5--8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3--5条最为合理。

# GCD和NSOperation的比较

  1. GCD 的核心是 C 语言写的系统服务,执行和操作简单高效,因此 NSOperation 底层也通过 GCD 实现,换个说法就是 NSOperation 是对 GCD 更高层次的抽象,这是他们之间最本质的区别。因此如果希望自定义任务,建议使用 NSOperation;

  2. 依赖关系,NSOperation 可以设置两个 NSOperation 之间的依赖,第二个任务依赖于第一个任务完成执行,GCD 无法设置依赖关系,不过可以通过dispatch_barrier_async来实现这种效果;

  3. KVO(键值对观察),NSOperation 和容易判断 Operation 当前的状态(是否执行,是否取消),对此 GCD 无法通过 KVO 进行判断;

  4. 优先级,NSOperation 可以设置自身的优先级,但是优先级高的不一定先执行,GCD 只能设置队列的优先级,无法在执行的 block 设置优先级;

  5. 继承,NSOperation 是一个抽象类,实际开发中常用的两个类是 NSInvocationOperation 和 NSBlockOperation ,同样我们可以自定义 NSOperation,GCD 执行任务可以自由组装,没有继承那么高的代码复用度;

  6. 效率,直接使用 GCD 效率确实会更高效,NSOperation 会多一点开销,但是通过 NSOperation 可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己;

# GCD中的队列

GCD中的队列主要有以下四种

  • 主队列:由系统创建的串行队列

    获取方式: dispatch_get_main_queue()

  • 全局队列:由系统创建的并发队列

    /全局并发队列的获取方法 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

    //优先级从高到低(对应的服务质量)依次为

    • DISPATCH_QUEUE_PRIORITY_HIGH -- QOS_CLASS_USER_INITIATED
    • DISPATCH_QUEUE_PRIORITY_DEFAULT -- QOS_CLASS_DEFAULT
    • DISPATCH_QUEUE_PRIORITY_LOW -- QOS_CLASS_UTILITY
    • DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND
  • 串行队列:自定义的串行队列

    获取方式:dispatch_queue_create(@"队列名",DISPATCH_QUEUE_SERIAL)

  • 并行队列:自定义的并行队列

    获取方式:dispatch_queue_create(@"队列名",DISPATCH_QUEUE_CONCURRENT)

# 主队列添加同步任务

//主队列同步

// 不会开线程
    NSLog(@"start");
    // 等
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"a");
    });
    NSLog(@"b");
1
2
3
4
5
6
7

运行结果:

程序运行奔溃了,这是为啥子呢?主队列是串行的,执行 b必须要等 a执行完成,而a又必须等b执行完成,这样就互相等待了,就是我们常说的死锁。

当前的代码执行流程,默认就是主队列,也是一个串行队列,任务执行的顺序是:

NSlog(@"start") --> dispathc_sync任务块 --> NSlog(@"b") 在执行任务块时,会向主队列添加一个任务NSlog(@"a"),NSlog(@"a")需要等到NSlog(@"b")执行完成后才能执行,而NSlog(@"b")又要等dispathc_sync任务块执行完成才会执行。这样主队列就会进入一个互相等待状态,这样就是死锁

解决办法:将main_queue主队列改成串行队列或者并发都可以

# 主队列添加异步任务

主队列添加异步任务不会阻塞,不会奔溃

# 并发队列添加异步任务

并发队列添加异步函数执行任务

每个任务复杂度基本一致,异步不会堵塞主线程,打印顺序是: a-e-b-d-c,dispathc_async会开启新的线程去执行其中的任务

# 并发队列添加同步任务

虽然队列是并发的,但是函数是同步,所以任务就是顺序执行,所以打印顺序为:a-b-c-d-e,同步还是不开启线程

# 串行队列添加同步任务

串行队列同步函数执行任务,顺序执行,不会开启新的线程执行任务

# 串行队列添加异步任务

串行队列添加异步函数执行,会开启新的线程执行任务

# 串行队列添加同步和异步混合

该案例和主队列添加同步任务是一样的,主队列也是串行队列。此案例也会崩溃!

dispatch_sync任务块没有执行完,bb 执行不了,dd 又等待 bb的执行,bb任务的执行和 dd 任务的执行互相等待了,死锁。

编辑 (opens new window)
上次更新: 2024/10/23, 23:26:17
Block系列
线程系列-锁

← Block系列 线程系列-锁→

最近更新
01
数组
10-25
02
数组双指针系列之对撞指针
10-25
03
数组双指针系列之快慢指针
10-25
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式