什么是进程、线程

进程就是一个应用程序的工作空间,比如你打开的QQ,微信,工作空间包含了该程序运行所需的所有资源。而线程是进程中的执行单位,一个进程最少有一个线程。

进程与线程对比

  • 「进程是系统资源分配和调度的最小单位」
  • 「线程是程序执行的最小单位」
  • 一个进程由一个或多个线程组成,线程是进程中代码的不同执行路线
  • 进程之间相互独立,进程中的线程共享程序的内存空间及资源
  • 线程在时间效率和空间效率都比进程要高

    协程

    协程是一种用户态的轻量级线程,线程是CPU来调度,而协程的调度完全是由用户来控制的。

    协程与线程对比

  • 一个线程可以有多个协程

  • 线程、进程都是同步机制,而协程是异步
  • 协程可以保留上一次调用时的状态,当过程重入时,相当于进入了上一次的调用状态
  • 协程是需要线程来承载运行的,所以协程并不能取代线程,「线程是被分割的CPU资源,协程是组织好的代码流程」

    并发、并行

  • 并发(concurrency):把任务在不同的时间点交给处理器进行处理,在同一时间点任务并不会同时运行。

  • 并行(parallelism):将每个任务分配给每个处理器独立来完成,在同一个时间点,任务一定是同时运行的。
  1. 并发和并行是相对于进程或者线程来说的。
  2. 并发是一个或多个CPU对多个进程/线程之间的多路复用,通俗讲就是CPU轮流执行多个任务,而每个任务都执行一小段,从宏观来看就像在同时执行。
  3. 并行必须有多个CPU来提供支持,真正意义上的在同一时刻执行多个进程或线程。

Goroutine 与线程的区别

goroutine 优点如下:

  • goroutine 均在栈中进行管理,内存消耗更少:Goroutine 所需要的内存通常只有 2kb,而线程则需要 1Mb(500 倍)
  • 开销更小:线程的并发是操作系统级别控制的申请资源、销毁资源等。而 goroutine 所占用的栈空间大小,由 runtime 按需进行分配,用 runtime 自己管理自己,所以开销小。在应用层模拟的线程,它避免了上下文切换的额外耗费,兼顾了多线程的优点。简化了高并发程序的复杂度
  • 以 64位环境的 JVM 为例,会默认固定为每个线程分配 1MB 栈空间,如果大小分配不当,便会出现栈溢出的问题

image.png

goroutine内存消耗

测试机器环境

Mac
2.4 GHz 八核Intel Core i9
32G内存
go version go1.16.6 darwin/amd64

测试方法

  1. 创建大量goroutine
  2. 每个goroutine不做任何事情,且阻塞不退出
  3. 统计创建前和创建后的内存消耗,计算平均值
  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. "sync"
  6. )
  7. func main() {
  8. //runtime.GOMAXPROCS(3)
  9. var channel <-chan interface{}
  10. var wg sync.WaitGroup
  11. const numGoroutines = 1000000 // 1M
  12. memConsumed := func() uint64 {
  13. runtime.GC()
  14. var s runtime.MemStats
  15. runtime.ReadMemStats(&s)
  16. return s.Sys
  17. }
  18. blockFunc := func() {
  19. wg.Done()
  20. <-channel
  21. }
  22. wg.Add(numGoroutines)
  23. before := memConsumed()
  24. for i := numGoroutines; i > 0; i-- {
  25. go blockFunc()
  26. }
  27. wg.Wait()
  28. after := memConsumed()
  29. fmt.Printf("%.3fkb", float64(after-before)/numGoroutines/1024)
  30. }
  31. // 2.524kb

创建1M goroutine, 平均每个goroutine消耗内存 2.524kb

参考

《Concurrency in Go》