线程与进程

线程定义

  • 线程是进程的基本执行单元,一个进程的所有任务都是在线程中执行
  • 进行要执行任务,必须得有线程,进行至少有一条线程
  • 程序启动会默认开启一条线程,这条线程被称为主线程/UI线程

进程定义

  • 进程是系统进行资源和高度的基本单位
  • 在移动端进程是指在系统中正在运行的一个程序
  • 每个进程之间是独立的,每个进程均运行在专用的且受保护的内存中

进程与线程的关系

  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间
  • 资源拥有:同一进程内的线程共享本进程的资源内存、I/O、CPU等,但是进程之间的资源是独立的
  • 相互影响:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃后整个进程就死掉了,所以多进程要比多线程健壮
  • 资源占用:进程切换时资源消耗大,效率高;所以涉及到频繁切换时,使用线程要好于进程,同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程
  • 执行过程:每个独立的进程有一个程序入口、顺序执行序列和程序出口。但是线程不能独立执行,必须存在应用程序中(进程),有应用程序提供多个线程执行控制(多线程开发)
  • 线程是处理器调试的基本单位,但进程不是,线程也是进行执行的基本单元

线程与进程的关系

  • 多线程中的队列有:串行队列,并发队列,全局队列,主队列
  • 队列可以理解为一种数据结构,以某种方式,等待线程去执行

iOS线程与RunLoop的关系

  • 线程与RunLoop是一一对应的,一个RunLoop对应一个核心线程,为什么说是核心线程,因为RunLoop是可以嵌套的,但是核心只有一个,他们的对应关系保存在一个全局字典里
  • RunLoop是用来管理线程的,线程执行完任务时会进入休眠状态,有任务进来时会被唤醒,开始执行任务,所以说RunLoop是事件驱动的
  • RunLoop在第一次获取时被创建,线程结束时被销毁,主线程的RunLoop在程序启动的时候就会被创建
  • 子线程的RunLoop是懒加载的,只有在使用的时候才会被创建

注:在子线程使用NSTimer时要注意确保子线程的RunLoop已经创建,否则NSTimer不会生效

多线程

定义

  • 多线程是一个进程中并发多个线程同时执行各自的任务,就是由单核CPU通过时间片不断地切换执行任务

原理

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

多线程的优缺点

优点:

  • 减少应用程序的堵塞,增加程序的执行效率
  • 适应提高CPU和内存的利用率
  • 线程上的任务执行完成后,线程自动销毁

缺点:

  • 线程的开启需要占用一定的内存空间,默认是512kb/线程
  • 线程开启的越多内存占用越大,会降低程序的性能
  • 线程越多CPU在调用线程上的开销就越大
  • 程序设计更加复杂,需要考虑线程间的通信,多线程的数据共享问题

iOS多线程常见的技术方案

方案 简介 语言 线程生命周期 使用频率
pthread 一套通用的多线程API 适用于Unix/Linux/Windows等系统 跨平台、可移植 使用难度大 C 程序员管理 几乎不用
NSThread 使用更加面向对象 简单易用、可直接操作线程对象 OC 程序员管理
GCD 旨在替代NSThread等线程计数 充分利用设备的多核 C 自动管理 经常使用
NSOperation 基于GCD(底层实现是GCD) 比GCD多了一些更简单实用的功能 使用更加面向对象 OC 自动管理 经常使用

GCD

全称Grand Central Dispatch,由C语言实现,是苹果为多核的并行运算提出的解决方案,GCD会自动利用更多的CPU内核,自动管理线程的声明周期,程序员只需要告诉GCD需要执行的任务,无需编写任何管理线程的代码。GCD也是iOS使用频率最高的多线程技术。

优点:

  • GCD 可用于多核的并行运算
  • GCD 会自动利用更多的CPU内核(比如双核,四核)
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

GCD任务和队列

任务

任务就是执行操作的意思,也就是在线程中执行的那段代码。
在GCD中是放在block中的。

  • 执行任务有两种方式:[同步执行]和[异步执行]
  • 两者的区别:是否等待队列的任务执行结束,以及是是否具备开启新线程的能力

同步执行(sync):

  • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行
  • 只能在当前线程中执行任务,不具备开启新线程的能力

异步执行(async):

  • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
  • 可以在新的线程中执行任务,具备开启新线程的能力

队列

队列是一种特殊的线性表,采用FIFO(先进先出)的原则。

  • GCD中有两种队列:[串行队列]和[并发队列],两者都符合FIFO原则
  • 两者的区别:执行顺序不同,以及开启线程数不同

串行队列(Serial Dispatch Queue):

  • 每次只有一个任务被执行,让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

并发队列(Concurrent Dispatch Queue):

  • 可以让多个任务并发(同时)执行,(可以开启多个线程,并且同时执行任务)

注:并发队列的并发功能只有在异步(dispatch _async)方法下才有效

GCD的常用函数

  • GCD中有2个用来执行任务的函数
    • 用同步的方式执行任务
      1
      2
      3
      dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
      queue:队列
      block:任务
    • 用异步的方式执行任务
      1
      dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

GCD源码:https://github.com/apple/swift-corelibs-libdispatch

各种队列的执行效果

并发队列 手动创建的串行队列 主队列
同步(sync) 没有开启新线程 串行执行任务 没有开启新线程 串行执行任务 没有开启新线程 串行执行任务
异步(async) 有开启新线程 并发执行任务 有开启新线程 串行执行任务 没有开启新线程 串行执行任务

使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)

GCD队列组的使用

  • 异步并发执行任务1,任务2
  • 待任务1,任务2都执行完毕后,再回到主线程执行任务3
  • 待任务1,任务2都执行完毕后,回到子线程,并发执行任务5和任务6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 // 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);

// 添加异步任务
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1-%@", [NSThread currentThread]);
}
});

dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2-%@", [NSThread currentThread]);
}
});

// 等前面的任务执行完毕后,会自动回到主线程执行这个任务
// dispatch_group_notify(group, queue, ^{
// dispatch_async(dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任务3-%@", [NSThread currentThread]);
// }
// });
// });
// 等前面的任务执行完毕后,会自动回到主线程执行这个任务,此方法更加简单粗暴,
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任务4-%@", [NSThread currentThread]);
// }
// });

// 回到子线程,并发执行任务3和任务4
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务5-%@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务6-%@", [NSThread currentThread]);
}
});

参考