多线程之基础概念

本文主要对比了iOS多线程编程中所涉及的一些概念

  • 进程 vs. 线程
  • 同步 vs. 异步
  • 串行 vs. 并发
  • 并行 vs. 并发
  • 任务 vs. 队列
  • 队列 vs. 线程
  • 同步 vs. 互斥
  • 异步 + 串行
  • 异步 + 并发
  • 同步 + 串行(很少使用、可能死锁)
  • 同步 + 并发(很少使用)

进程 vs. 线程

  • 进程(process),指的是一个正在运行中的可执行文件。每一个进程都拥有独立的虚拟内存空间和系统资源,包括端口权限等,且至少包含一个主线程和任意数量的辅助线程。另外,当一个进程的主线程退出时,这个进程就结束了;
  • 线程(thread),指的是一个独立的代码执行路径,也就是说线程是代码执行路径的最小分支(CPU调度的最小单位,不具有独立内存,多个线程共享进程的资源)。在 iOS 中,线程的底层实现是基于 POSIX threads API 的,也就是我们常说的 pthreads ;
  • 总结:进程是资源分配的基本单位,线程是调度的基本单位。进程包含线程,线程共用进程的资源

同步 vs. 异步

官方解释

When a work item is executed synchronously with the sync method, the program waits until execution finishes before the method call returns

When a work item is executed asynchronously with the async method, the method call returns immediately

  • 同步操作:会等待操作执行完成后再继续执行接下来的代码
  • 异步操作:会在调用后立即返回,不会等待操作的执行结果。
  • 区别:在于是否等待操作执行完成,亦即是否阻塞当前线程。

假如我有A,B,C三个任务,

  • 如果这三个任务都是同步执行, 程序将等待A 执行完毕之后, 再执行B, 再执行C
  • 如果这三个任务都是是异步执行, 程序直接跳过A,B,C,执行后面的代码, 执行完毕之后, 再来执行A,B,C中的任务
另外, 还有一点需要明确的是
  • 同步执行没有开启新线程的能力, 所有的任务都只能在当前线程执行
  • 异步执行有开启新线程的能力, 但是, 有开启新线程的能力, 也不一定会利用这种能力, 也就是说, 异步执行是否开启新线程, 需要具体问题具体分析

串行 vs. 并发

  • 串行:指的是一次只能执行一个任务,必须等一个任务执行完成后才能执行下一个任务
  • 并发:指的是允许多个任务同时执行。

并行 vs. 并发

  • 并发:当有多个线程在操作时,如果系统只有一个CPU,把CPU运行时间划分成若干个时间段,分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态。这种方式我们称之为并发(Concurrent)。并发=间隔发生
  • 并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。 并行=同时进行
  • 区别:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。

任务 vs. 队列

  • 任务(Task): 指的是我们需要执行的操作,是一个抽象的概念,用通俗的话说,就是一段代码
  • 队列(Queue): 存放任务的容器,遵循FIFO(first in first out, 先进先出原则)

队列 vs. 线程

  • iOS 系统就是使用队列来进行任务调度,它会根据调度任务的需要和系统当前的负载情况动态地创建和销毁线程,而不需要我们手动地管理,在 iOS 中,有两种不同类型的队列,分别是串行队列和并发队列。串行队列一次只能执行一个任务,而并发队列则可以允许多个任务同时执行
  • 总结:iOS系统通过队列和管理任务,会根据任务自动管理线程。

同步 vs. 互斥

  • 互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。  
  • 同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

异步 + 串行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)asyncSerial {
/* 1. 创建一个串行队列 */
dispatch_queue_t serialQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_SERIAL);

/* 2. 将不同的任务添加到队列中 */
dispatch_async(serialQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});

dispatch_async(serialQueue, ^{
NSLog(@"download2--------%@",[NSThread currentThread]);
});

dispatch_async(serialQueue, ^{
NSLog(@"download3--------%@",[NSThread currentThread]);
});
}

结论:开启了新的线程, 但是不管任务有多少个, 只开启一条新的线程, 任务的执行顺序也是按照队列中的顺序执行的, 因为同一条线程中, 必须等到前一个任务执行完毕后, 才能执行下一个任务.

异步 + 并发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)asyncConcurrent {

/* 1. 创建一个并发队列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_CONCURRENT);

/* 2. 将任务放到队列中, 下面的代码将三个任务放到队列中 */
dispatch_async(concurrentQueue, ^{
NSLog(@"download1-------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"download2-------%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"download3-------%@",[NSThread currentThread]);
});
}

结论:开启了不同的线程, 任务完成的顺序也是随机的,但是开启的线程数量不是无限的,到了上限之后就会复用之前的线程。

同步 + 串行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)syncSerial {
/* 1. 创建串行队列 */
dispatch_queue_t serialQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_SERIAL);

/* 2. 将任务放到队列中 */
dispatch_sync(serialQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});

dispatch_sync(serialQueue, ^{
NSLog(@"download2--------%@",[NSThread currentThread]);
});

dispatch_sync(serialQueue, ^{
NSLog(@"download3--------%@",[NSThread currentThread]);
});
}

结论:没有开启新线程,会阻塞当前线程,按照添加到队列的顺序执行。如果是当前线程同步到当前线程会造成死锁。

相当于方法A调用了方法B执行,方法B执行时发现需要等待方法A执行结束才能执行,所以会造成A等待B,B等待A的死循环情况。

同步 + 并发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)syncConcurrent {

/* 1. 创建一条并发队列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("download.tsaievan.com", DISPATCH_QUEUE_CONCURRENT);

/* 2. 把任务放到队列中 */
dispatch_sync(concurrentQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});

dispatch_sync(concurrentQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});

dispatch_sync(concurrentQueue, ^{
NSLog(@"download1--------%@",[NSThread currentThread]);
});

}

结论:没有开启新线程,会阻塞当前线程,执行顺序按照添加到队列的顺序执行。

总结