多线程和多进程都是为了解决程序的并发问题。
并发
并发 (Concurrency) 是指多个任务可以在重叠的时间段内开始、执行及完成。 适当的工具选择取决于要执行的任务(CPU密集型或IO密集型)和偏好的开发风格(事件驱动的协作式多任务或抢占式多任务处理)。
一间工厂(Process)拥有资源与设备但需要由员工(Thread)去操作才能生产产品。 所以要做出一件成品,工厂内至少要有一位员工在做事。 以电脑分时运作架构来看,不同的行程就像是不同的工厂,而执行绪则像是一位派遣员工一样由操作系统负责调度他在什么时间该去那家工厂上班。
如果电脑拥有多个处理核心,即代表系统可以同时调用的员工数目增加,所谓人多好办事,如此一来系统在为执行绪安排工作时就有两种策略:
- 第一种策略是在同一时间内为各家工厂都分配一个员工去作事,这种方式称作多进程(Multi-processing)平行执行。 跟单一行程处理比起来,其优点在于可以在相同的时间内完成较多的工作。
- 另一种策略是在同一时间内把所有员工都派到同一家工厂去工作,此法称做多线程(Multi-threading)平行执行。 相较于单一线程处理方式,它有机会让相同的工作在比较短的时间内完成。
并行
并发:在单核机器上,多任务以cpu时间片共享的方式运行。
并行:在多核机器上,每个核同时运行多个任务。
对多线程来说,这两个概念大部分是重叠的。这两个词是用来描述硬件同时执行多个任务的方式,而“并行”更加注重性能。使用硬件提高数据处理速度时,会讨论程序的并行性。当关注重点在于任务分离或任务响应时,会讨论程序的并发性。这两个术语存在的目的,就是为了区别多线程中不同的关注点。
任务并行与数据并行
有两种利用并发来提高性能的方式:第一,将一个单个任务分成几部分并行运行,从而降低总运行时间,这就是任务并行(task parallelism)。虽然,这听起来很直观,但是一个相当复杂的过程,因为各个部分之间可能存在着依赖。区别可能是在过程方面——一个线程执行算法的一部分,而另一个线程执行算法的另一个部分——或是在处理数据——每个线程在不同的数据块上执行相同的操作(第二种方式)。后一种方法被称为数据并行(data parallelism)。
进程
进程是资源分配的最小单位。进程(Process)为已经执行并且加载到内存中的程序(Program),进程中的每一行代码随时都有可能被 CPU 执行。 在实际生活中,点开应用程序就是将 Program 活化成 Process,我们在任务管理器 (windows) 或 jobs(linux)中看到 PID,也就是执行中的 Process ID。 Program 是工厂蓝图,Process 就是照着设计蓝图所完成的实体工厂。
- Process 是电脑中已执行 Program 的实体,每一个 Process 互相独立。
- Process 需要一些资源才能完成工作,如CPU、内存、文件以及 I/O 设备。
- Process 不是基本执行单位,而是 Thread (执行绪) 的容器。 每个 Process由一个 Memory Space 和一个以上的 Thread 所组成。
线程
线程(Thread )是CPU调度的最小单位。前面有提到 Process 是 Thread 的容器,在同一个 Process 中会有很多个 Thread,每一个 Thread 负责某一项功能。 以微信 Process 为例,可以同时接受对方传来的讯息以及发送自己的讯息给对方,就是同个 Process 中不同 Thread 的功劳。 Thread 就是实体工厂内的工人,确保工厂的每项功能,并且共享工厂内的每一项资源。
特点
CPU密集型:多进程
IO密集型:多线程
NO | 进程 | 线程 |
---|---|---|
1. | 进程意味着任何程序都在执行中。 | 线程表示进程的一段。 |
2. | 该过程需要更多时间才能终止。 | 线程终止所需的时间更短。 |
3. | 创建需要更多时间。 | 创建所需的时间更少。 |
4. | 上下文切换也需要更多时间。 | 上下文切换所需的时间更少。 |
5. | 该过程在沟通方面效率较低。 | Thread 在通信方面更有效率。 |
6. | 多进程编程包含多进程的概念。 | 对于多个线程,我们不需要运行多个程序,因为单个进程由多个线程组成。 |
7. | 该过程是隔离的。 | 线程共享内存。 |
8. | 该过程称为重量级过程。 | 线程是轻量级的,因为进程中的每个线程共享代码、数据和资源。 |
9. | 进程交换使用操作系统中的接口。 | 线程切换不需要调用操作系统,并导致内核中断。 |
10. | 如果一个进程被阻止,则它不会影响其他进程的执行 | 如果用户级线程被阻止,则所有其他用户级线程都将被阻止。 |
11. | 该进程有自己的进程控制块、堆栈和地址空间。 | Thread 具有父 PCB、自己的线程控制块以及堆栈和公共地址空间。 |
12. | 对父进程的更改不会影响子进程。 | 由于同一进程的所有线程共享地址空间和其他资源,因此对主线程的任何更改都可能影响进程中其他线程的行为。 |
13. | 其中涉及系统调用。 | 不涉及系统调用,它是使用 API 创建的。 |
14. | 进程交换使用操作系统中的接口。 | 线程相互共享数据。 |
关联性
线程在进程下行进(单纯的车厢无法运行)
一个进程可以包含多个线程(一辆火车可以有多个车厢)
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到该趟火车的所有车厢)
进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-互斥锁(mutex)
进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-信号量(semaphore)
死锁
在多线程中(Multithreading),两个线程若同时访问或改变全局变量(Global Variable),可能会发生同步(Synchronization)问题。 若执行绪之间互抢资源,则可能产生死锁(Deadlock)。
死锁条件发生的4中情况:
- 同一个资源不能同时给两个人用。
- 有一个人拿了一个资源,又想拿别人的资源。
- 如果一个人占用资源很久,仍不能赶他走。
- A 等 B,B 等 C,C 等 D,D 又等 A,等成一圈。
守护线程
在一个含有线程的程序中,当主线程的代码运行完之后,如果还有其他子线程还未执行完毕,那么主线程会等待子线程执行完毕之后,再结束;如果有一个线程必须设置为无限循环,那么该线程不结束,意味着整个程序就不能结束,那为了能够程序正常退出,将这类无限循环的线程设置为守护线程。
当程序当中仅仅剩下守护线程时,程序就能够正常退出,不必关心这类线程是否执行完毕,这就是守护线程的意义。
主线程结束了,系统就会通知守护线程结束。
未完待续~
参考
- https://stackoverflow.com/questions/1897993/what-is-the-difference-between-concurrent-programming-and-parallel-programming
- https://joearms.github.io/published/2013-04-05-concurrent-and-parallel-programming.html
- https://zhuanlan.zhihu.com/p/115270781