iOS多线程-NSThread

539 阅读5分钟

多线程

同步、异步 和 串行、并行/并发

在正式开始前,先回顾一下这四者的区别!

  • 同步:多个任务情况下,一个任务A执行结束,才可以执行另一个任务B。只存在一个线程

    img

  • 异步:多个任务情况下,一个任务A正在执行,同时可以执行另一个任务B。任务B不用等待任务A结束才执行。存在多条线程

    img

    并行和并发是异步的两种形式

  • 并行:并行才是真正的异步,多核CUP可以同时开启多条线程供多个任务同时执行,互不干扰,如上图的并行,其实和异步图例一样。

    img

  • 并发:其实是伪异步,在单核CUP中只能有一条线程,但是又想执行多个任务。这个时候,只能在一条线程上不停的切换任务,让用户以为是在同时执行

    img

进程和线程

进程:一个正在运行的应用程序

线程:一个进程(应用程序)至少有一条线程,进程的所有任务都在线程中执行

线程的串行

  • 一个线程中任务的执行是串行的

  • 如果一个线程中执行多个任务,那么只能一个个按照顺序执行

线程和进程的区别

  1. 线程是CPU调用(执行任务的最小单位)
  2. 进程是CPU分配资源和调度的单位(CPU是不会分配资源给线程的)
  3. 一个程序可以对应多个进程,一个进程中可以有多个线程,但至少有一个线程
  4. 同一个进程内的线程共享进程的资源

其实可以把进程比作一个工厂,而进程就是工厂里的工人

多线程

刚刚上面说到了,一个进程中至少要有一条线程,那么多条线程即多线程,每条线程可以并行(同时)执行不同的任务,可以提高程序的执行效率

  • 线程内部任务的执行是串行的
  • 线程与线程之间任务的执行是可以并行的

原理

  • 其实,在同一个时间,CPU只能处理一条线程
  • 刚刚上面说的每条线程可以同时执行不同的任务,其实是CPU快速的在多条线程之间调度,而这过程非常快,因此造成了多线程并行的假象(这里自己再去注意并行和并发的区别)
  • 所以,其实真正的多线程是并发

思考:线程非常多时,会怎么样?

  • CPU在调度线程时也需要消耗资源,如果线程非常多,会导致CPU的消耗极高
  • 每条线程被调度执行的频次会降低(线程的执行效率降低)

多线程的优缺点

优点:

  • 能适当提高程序的执行效率
  • 能适当提高资源利用率(CPU、内存利用率) 缺点:
  • 创建线程需要开销
  • 如果开启大量的线程,会降低程序的性能
  • 线程越多,CPU在调度线程的开销越大
  • 程序设计更复杂:比如线程之间的通信、多线程的数据共享

主线程

一个iOS程序运行后,默认为开启一条线程,称为“主线程”或“UI线程”

主要作用

  • 显示/刷新UI界面
  • 处理UI事件(点击、滚动等) 注意:只要凡是和UI相关的事件,就必须在主线程中实现

注意点

  • 较耗时操作不要放到主线程,若放到主线程,会导致界面卡顿

    • 因为线程内部的执行是串行的
  • 将耗时操作放在子线程(说法不一样,也可以叫后台线程、非主线程)

获取线程

  1. 获得主线程
NSThread *mainThread = [NSThread mainThread];
  1. 获取当前进程
NSThread *currentThread = [NSThread currentThread];
  1. 判断主线程
    1. 打印线程,若其number=1,那就是主线程
//2. 类方法
BOOL isMainThreadA = [NSThread isMainThread];

//3. 对象方法
NSThread *currentThread = [NSThread currentThread];
BOOL isMainThreadA = [currentThread isMainThread];

iOS中多线程的实现方案

创建和启动线程

一个NSThread对象就代表一条线程

  1. 方法一 需要创建和启动线程 还可以设置线程名称和线程优先级

  2. 方法二

  3. 方法三 后面两个方法创建线程虽然简单快捷,但是只有方法一可以拿到线程对象

NSThread线程的生命周期

你可能会有一个疑惑:在创建线程的函数中,一旦函数走到它自己的 } 时,函数里的局部变量便都会被释放,那么是如何在程序的其他部分取得该线程对象的呢?

这时候我们就要来思考一下NSThread线程的生命周期了~

当任务执行完毕之后,线程才会被释放

(一定要清楚线程的生命周期!)

线程的状态

就绪、执行、阻塞、死亡

  • 阻塞:线程还在内存中,但是不在可调度线程池中
  • 死亡:线程从可调度线程中移出到内存,再在内存中释放 (所以线程死亡后,你再用start方法是不能重新开启线程的,因为该线程已经不在内存中了)

多线程的安全隐患

  1. 资源共享
  • 一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
  • 例如多个线程访问同一个对象、同一个文件... 那么就容易引发数据错乱和数据安全问题

这是一个典型的例子

解决方法——互斥锁

  • 互斥锁必须是全局唯一的
  • 互斥锁能有效防止因多线程抢夺资源造成的数据安全问题
  • 会消耗大量的CPU资源
  • 会导致线程同步

原子性和非原子性

  • nonatomic:非原子属性,不会为setter加锁
    • 非线程安全
    • 适合内存小的移动设备
  • atomic:原子属性,为setter方法加锁
    • 线程安全
    • 消耗资源

一般开发情况都用nonatomic

线程间的通信

  • 数据传递
  • 任务传递