Apple开发_多线程_基础知识点

1、Threads

  • 1.1 进程

    • 进程是指在系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。
    • 比如同时打开 QQ、Xcode,系统就会分别启动两个进程。通过 “活动监视器” 可以查看 Mac 系统中所开启的进程。
    • 一个程序的一次运行,在执行过程中拥有独立的内存单元,而多个线程共享一块内存。
  • 1.2 线程

    • 线程是进程中执行任务的基本执行单元。一个进程要执行任务,必须得有线程,一个进程(程序)的所有任务都在线程中执行。每一个进程至少有一条线程,即主线程。一个进程可以开启多条线程,每条线程可以并发(同时)执行不同的任务。
    • 比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行。
    • 在程序中每一个方法的执行,都是从上向下串行执行的。除非使用 block,否则在一个方法中,所有代码的执行都在同一个线程上。
    • 进程与线程的联系:线程是进程的基本组成单位。
    • 进程与线程的区别:
        1. 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
        1. 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
        1. 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
        1. 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

2、线程的串行

  • 一个线程中任务的执行是串行(顺序执行)的。如果要在一个线程中执行多个任务,那么只能一个一个的按顺序执行这些任务,也就是说,在同一时间内,一个线程只能执行一个任务。
  • 比如在一个线程中下载三个文件(分别是文件 A,文件 B,文件 C)。

3、多线程原理

  • 同一时间,CPU 只能处理一条线程,只有一条线程在工作(执行)。多线程并发(同时)执行,其实是 CPU 快速的在多条线程之间调度(切换)。如果 CPU 调度线程的时间足够快,就造成了多条线程并发执行的假象。
  • 多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:
    • 1)首先确保单个线程执行正确。
    • 2)添加线程。
  • 在多线程开发的时候,有几点提示:
    • 1)不要相信一次运行的结果。
    • 2)不要去做不同线程之间执行的比较,线程内部的方法都是各自独立执行的。
    • 3)多线程的目的是将耗时的操作放在后台,不阻塞主线程和用户的交互。
    • 4)多线程开发的原则是简单,不要将简单的事情搞复杂了。

4、多线程优点、缺点

  • 4.1 多线程优点

    • 使用多线程可以将耗时的任务放到后台去执行,不阻塞主线程和用户的交互。
    • 使用多线程能够并发、同时执行多个任务,可以适当提高程序的执行效率,适当提高资源的利用率(CPU、内存的利用率)。
  • 4.2 多线程缺点

    • 每开一个线程都会造成系统额外的负担,开启线程需要占用一定的内存空间(默认情况下,每一条线程都会占用 512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能。
    • 线程越多,CPU 调度线程上开销就越大。
    • 线程越多,程序设计就更加复杂,比如线程之间的通信、多线程的数据共享就更加复杂。
  • 通常开启 5 到 6 条线程即可,不能开启的太多。
  • 栈区:保存局部变量和函数的参数,由下向上压栈。线程执行完毕后,栈区会自动释放,程序员不需要管理栈区的内存。

5、主线程

  • 一个 iOS 程序运行后,默认会开启一条线程,称为主线程或 UI 线程。主线程主要用于显示/刷新 UI 界面,处理 UI 事件(比如点击事件、滚动事件、拖拽事件等)。
  • 注意别将比较耗时的操作放到主线程中,耗时的操作会卡住主线程,严重影响 UI 的流畅度,给用户一种 “卡” 的坏体验。

6、线程状态

  • 新线程创建启动后,线程对象被加入可调度线程池中,进入就绪状态,等待 CPU 调度。在线程被调度运行时,如果线程调用了 sleep 方法或者等待同步锁时,线程由运行状态进入到阻塞状态,线程被移出可调度线程池。sleep 到时或者得到同步锁时,线程重新进入可调度线程池,回到就绪状态。线程任务执行完毕或者异常、强制退出时,线程由运行状态进入到死亡状态,线程结束,线程占用内存空间被释放。
  • 在整个线程状态循环中,程序员无法控制线程的运行状态。线程是否运行由 CPU 调度完成。

7、线程安全

  • 如果一个属性,在多个线程执行的情况下,仍然能够得到正确结果,被称为线程安全。
  • 要实现线程安全,就必须要用到锁,用到锁就会降低程序的性能。为了得到更佳的用户体验,UIKit 不是线程安全的,UI 线程约定所有更新 UI 的操作都必须主线程上执行,因此,主线程又被称为 UI 线程。
  • 1)iOS 开发建议:

    • 所有属性都声明为 nonatomic。
    • 尽量避免多线程抢夺同一块资源。
    • 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力。
  • 2)使用互斥锁:

    @synchronized (self) {
    }
    
    • 互斥锁能够保证锁定范围的代码,同一时间,只有一条线程能够执行。
    • 互斥锁的锁定范围,应该尽量小,只要锁住资源读写部分的代码即可。锁定范围越大,效率越差。使用锁之后,程序执行性能都会受到影响,会影响并发的目的。
    • 在 iOS 开发中,使用锁的机会极少,一般都是从服务器获取到数据,直接显示。
    • 参数:
      • self 表示当前的 UIViewController,为 NSObject 的子类。本质上是任意一个 NSObject 子类对象都可以当成锁。
      • 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个锁对象。
      • 锁对象一定要保证所有的线程都能够访问。必须为全局变量。
  • 3)使用变量原子性:

    • 原子属性(线程安全),是针对多线程设计的,是默认属性。
    • 多个线程在写入原子属性时(调用 setter 方法),能够保证同一时间只有一个线程执行写入操作,但是允许多个线程同时读取属性值。
    • 原子属性是一种单(线程)写多(线程)读的多线程技术,在多线程读取数据时,有可能出现“脏”数据 - 读取的数据可能会不正确。
    • 原子属性内部也有一把 "锁",是自旋锁,执行效率比互斥锁高。
    • 在定义属性时,如果不需要考虑线程安全,要显示地指定为 nonatomic。
    • 原子属性,解决不了卖票问题,因为卖票的读写都需要锁定。
    • 如果重写了原子属性的 setter 方法,相当于覆盖了系统提供的 setter 方法,此时,系统要求,必须重写 getter 方法。
    • 定义属性时,系统默认提供 getter & setter 方法,并且生成一个 _成员变量,但是如果自己重写了 getter & setter 方法,_成员变量,不会自动生成。
    • @synthesize 合成指令,可以指定保存属性数值的 成员变量。
    • 在 Xcode 4.5 之前,开发,程序员必须自己实现 @synthesize 指令。
  • 4)自旋锁 & 互斥锁:

    • 共同点:
      • 都能够保证同一时间,只有一条线程执行锁定范围的代码。
    • 不同点:
      • 互斥锁:如果发现有其他线程正在执行锁定的代码,线程会进入休眠状态,等待其他线程执行完毕,打开锁之后,线程会被唤醒。
      • 自旋锁:如果发现有其他线程正在执行锁定的代码,线程会以死循环的方式,一直等待锁定代码执行完成。
    • 结论:
      • 自旋锁更适合执行非常短的代码。
      • 无论什么锁,都是要付出代价。

8、线程间通信

  • 在子线程中执行比较耗时的操作(如下载图片等),子线程执行完毕后通知主线程更新 UI 等的操作。
  • UIKit 中几乎所有控件都不是线程安全的,因此需要在主线程上更新 UI。在子线程中可以使用以下方法让主线程执行 UI 操作:
    // waitUntilDone: YES 等待主线程执行完成后子线程再继续执行
    [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateUI:image];
    });
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self updateUI:image];
    }];
    
posted @ 2022-03-14 14:13  CH520  阅读(72)  评论(0编辑  收藏  举报