查看原文
其他

Go必知必会:协程(上):进程、线程、协程的区别和联系

就业陪跑训练营 王中阳
2024-08-30

文末有面经共享群

本文来自极客学院专栏,欢迎订阅:Go入门进阶实战专栏:其实学Go很简单。

在 Go 语言中,Goroutine 是实现并发的核心机制,它提供了一种轻量级的线程,能够在用户态进行调度,从而避免了传统操作系统线程的上下文切换开销。Goroutine 的设计允许成千上万的并发执行,极大地提高了程序的并发性能。与协程相比,Goroutine 拥有更为高效的调度器和内存管理策略,使其在处理高并发任务时更加出色。此外,Go 的 Goroutine 还支持通过 channel 进行通信,简化了并发编程中的同步和通信问题。

进程

进程是操作系统进行资源分配和调度的一个基本单位。当一个程序被加载到内存并开始执行时,操作系统会为其创建一个进程,该进程在内存中拥有独立的内存空间和地址范围。

  1. 独立的内存体:每个进程都拥有自己的私有内存空间,包括代码段、数据段和堆栈等,确保了进程间的隔离。

  2. 地址空间:进程的地址空间由操作系统管理,它定义了进程可以访问的内存范围,保护了进程的安全性。

  3. 资源分配:操作系统以进程为单位分配系统资源,如 CPU 时间片和内存等,这确保了即使在多任务环境中,每个进程也能获得必要的资源以执行其任务。

  4. :进程的堆是用于动态内存分配的区域,它允许程序在运行时根据需要分配或释放内存。

  5. 操作系统管理:作为进程的上级管理实体,操作系统负责进程的创建、调度、同步和通信等管理任务。

通过这些机制,进程模型为程序提供了一个稳定和隔离的执行环境,同时允许操作系统高效地管理和调度系统资源。

示意图如下:

线程

线程,亦称为轻量级进程(LightWeight Process,LWP),是操作系统进行 CPU 调度的基本单位。它代表了程序执行的最小单元,具有以下特点。

  1. 调度的基本单位:线程是操作系统调度算法作用的直接对象,确保了任务的并发执行。

  2. 轻量级:与进程相比,线程的创建、同步和销毁需要的资源较少,切换成本也较低。

  3. 共享资源:同一进程内的线程共享进程的资源,如内存空间和文件描述符等,这促进了资源的有效利用。

  4. 独立执行流:每个线程拥有自己的程序计数器、栈和寄存器,能够独立执行指令流。

  5. 并发性:线程的引入使得在单个进程内部能够实现多个任务的并行或并发执行,提高了程序的响应性和效率。

通过线程的使用,操作系统和应用程序能够更加灵活地管理和优化任务的执行,满足现代多任务处理的需求。

示意图如下:

进程、线程的区别和联系

区别

  1. 调度:进程是资源的拥有者,而线程是执行和调度的基本单位。进程作为资源分配的实体,线程则负责在这些资源上执行任务。

  2. 并发性:进程允许在不同实例之间实现并发执行,而线程使得同一进程内的任务也能并发进行。

  3. 资源拥有

    1. 进程是资源的基本单位,拥有独立的地址空间和系统资源;

    2. 线程不直接拥有资源,但可以访问其所属进程的资源。

  4. 资源维护

    1. 进程维护静态资源,如地址空间、文件句柄等。

    2. 线程维护动态资源,如执行栈、控制信息等。

  5. 系统开销

    1. 创建或销毁进程的开销较大,因为它涉及资源的分配和回收;

    2. 线程由于共享进程资源,创建和销毁的开销相对较小。

  6. 稳定性与安全性

    1. 进程具有独立的内存空间,一个进程的崩溃不会直接影响其他进程;

    2. 线程作为进程的一部分,其稳定性较低,一个线程的失败可能影响整个进程。

  7. 地址空间与堆栈

    1. 线程共享进程的地址空间,拥有独立的堆栈和局部变量;

    2. 进程的终止意味着其所有线程的终止,而进程间的独立性更高。

联系

  1. 从属关系:一个线程总是隶属于某个进程,而一个进程可以包含多个线程。

  2. 资源共享:同一进程内的线程共享该进程的所有资源,包括内存、文件句柄等。

  3. 处理机分配:处理机(CPU)将资源分配给进程,而线程则是进程中实际执行的实体。

  4. 同步与通信

    1. 同一进程的线程间需要通过同步机制协作;

    2. 不同进程的线程间通过消息传递等方式实现通信。

  5. 执行单元:线程是进程内的执行单元,也是进程内可被调度的实体。

通过这些优化,我们更清晰地界定了进程和线程的不同角色和相互作用,同时强调了它们在系统资源管理和任务调度中的重要性。

举例说明进程和线程的区别

这幅图是一个双向多通道的道路图,假如我们把整个道路看做一个“进程”的话,那种图中由白色虚线分割开的车道就是进程中的各个“线程”了。

  1. 这些车道(线程)共享了道路(进程)的土地资源(系统资源)。

  2. 这些车道(线程)必须依赖于道路(进程),否则车道将没有意义(线程不能脱离于进程而存在)。

  3. 这些车道(线程)之间可以并发运行(各个车道你走你的,我走我的),也可以同步运行(遇到红灯的情况,需要等待特点条件后再执行)。

  4. 这些车道(线程)的运行和交通灯(代码逻辑)息息相关,如果交通灯出现问题(代码逻辑问题,比如死锁,多个线程同时竞争同一资源),会造成交通混乱。

  5. 这些车道(线程)谁先运行起来是未知的,只有当信号灯变化(分配 CPU 时间的时候)那刻才知道。

这个例子实在是香,太经典了!

进程/线程之间的亲缘性

亲缘性的意思是进程/线程只在某个 CPU 上运行(多核系统)。

使用 CPU 亲缘性的好处:防止进程/线程在 CPU 的多核间频繁切换,从而避免因切换带来的 CPU 的 L 1/L 2 cache 失效,cache 失效会降低程序的性能。

协程示意图如下:

  • 协程是一个比线程更轻量级的存在,协程完全由程序控制(也就是在用户态执行)

    • 协程不被操作系统内核所管理

    • 协程能极大的提升性能,不会像线程切换那样消耗资源

  • 子程序,又称为“函数”

    • 在所有语言中都是层级调用的,A 调用 B,B 调用 C,C 执行完毕返回,B 执行完毕返回,最终 A 执行完毕

    • 由此可见,子程序调用是通过栈实现的,一个线程就是执行一个子程序

线程(执行一个函数)、协程的区别和联系

  • 函数总是一个入口,一个返回,调用顺序是明确的(一个线程就是执行一个函数);

  • 而协程的调用和函数不同,协程在函数内部是可以中断的,可以转而执行别的函数,在适当的时候再返回来接着执行。

def A(){
print 1
print 2
print 3
}

def B(){
print 'x'
print 'y'
print 'z'
}

比如上面代码如果是协程执行,在执行 A 的过程中,可以中断去执行 B, 在执行 B 的时候亦然,结果可能是:1 x y 2 3 z。

同样上面的代码如果是线程执行,只能执行完 A 再执行 B,或者执行完 B 再执行 A,结果只可能是 2 种:1 2 3 x y z 或者 x y z 1 2 3。

协程和多线程的优势?为什么有了多线程还要引入协程?

  • 极高的执行效率:

    • 因为函数(子程序)不是线程切换,而是由程序自身控制的,因此没有线程切换的开销;

    • 和多线程比,线程数量越多,协程的性能优势越明显。

  • 不需要多线程的锁机制:

    • 因为只有一个线程,不存在同时写变量的冲突,在协程中控制共享资源不加锁,只需要判断状态就行了,因此执行效率比多线程高很多。

总结

我们上面的内容将其总结一下:

  • 进程是操作系统中的一个基本单位,它代表一个正在运行的程序或应用。

  • 线程是进程内部的一个基本单位,是进行任务调度的最小单位。线程共享进程的资源,如内存空间,但每个线程有自己的执行上下文(如寄存器状态、堆栈等)。

  • 协程是 Go 语言中的一种轻量级线程,它在软件层面(即程序层面)上利用单个操作系统线程来复用多个协程,以此减少内存开销。协程的调度由 Go 运行时(runtime)管理,而不是由操作系统直接管理,这使得协程的创建、切换和销毁开销非常小。

这篇文章通过对比的方式,让大家更好的理解进程、线程、协程间的区别和联系。

早日上岸!

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。

点击下方文章,看看他们是怎么找到好工作的!

这些朋友赢麻了!

我们又出成绩啦!大厂Offer集锦!遥遥领先!


继续滑动看下一个
王中阳
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存