什么是进程、线程
进程就是一个应用程序的工作空间,比如你打开的QQ,微信,工作空间包含了该程序运行所需的所有资源。而线程是进程中的执行单位,一个进程最少有一个线程。
进程与线程对比
- 「进程是系统资源分配和调度的最小单位」
- 「线程是程序执行的最小单位」
- 一个进程由一个或多个线程组成,线程是进程中代码的不同执行路线
- 进程之间相互独立,进程中的线程共享程序的内存空间及资源
-
协程
协程是一种用户态的轻量级线程,线程是CPU来调度,而协程的调度完全是由用户来控制的。
协程与线程对比
一个线程可以有多个协程
- 线程、进程都是同步机制,而协程是异步
- 协程可以保留上一次调用时的状态,当过程重入时,相当于进入了上一次的调用状态
协程是需要线程来承载运行的,所以协程并不能取代线程,「线程是被分割的CPU资源,协程是组织好的代码流程」
并发、并行
并发(concurrency):把任务在不同的时间点交给处理器进行处理,在同一时间点任务并不会同时运行。
- 并行(parallelism):将每个任务分配给每个处理器独立来完成,在同一个时间点,任务一定是同时运行的。
- 并发和并行是相对于进程或者线程来说的。
- 并发是一个或多个CPU对多个进程/线程之间的多路复用,通俗讲就是CPU轮流执行多个任务,而每个任务都执行一小段,从宏观来看就像在同时执行。
- 并行必须有多个CPU来提供支持,真正意义上的在同一时刻执行多个进程或线程。
Goroutine 与线程的区别
goroutine 优点如下:
- goroutine 均在栈中进行管理,内存消耗更少:Goroutine 所需要的内存通常只有 2kb,而线程则需要 1Mb(500 倍)
- 开销更小:线程的并发是操作系统级别控制的申请资源、销毁资源等。而 goroutine 所占用的栈空间大小,由 runtime 按需进行分配,用 runtime 自己管理自己,所以开销小。在应用层模拟的线程,它避免了上下文切换的额外耗费,兼顾了多线程的优点。简化了高并发程序的复杂度
- 以 64位环境的 JVM 为例,会默认固定为每个线程分配 1MB 栈空间,如果大小分配不当,便会出现栈溢出的问题
goroutine内存消耗
测试机器环境
Mac
2.4 GHz 八核Intel Core i9
32G内存
go version go1.16.6 darwin/amd64
测试方法
- 创建大量goroutine
- 每个goroutine不做任何事情,且阻塞不退出
- 统计创建前和创建后的内存消耗,计算平均值
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
//runtime.GOMAXPROCS(3)
var channel <-chan interface{}
var wg sync.WaitGroup
const numGoroutines = 1000000 // 1M
memConsumed := func() uint64 {
runtime.GC()
var s runtime.MemStats
runtime.ReadMemStats(&s)
return s.Sys
}
blockFunc := func() {
wg.Done()
<-channel
}
wg.Add(numGoroutines)
before := memConsumed()
for i := numGoroutines; i > 0; i-- {
go blockFunc()
}
wg.Wait()
after := memConsumed()
fmt.Printf("%.3fkb", float64(after-before)/numGoroutines/1024)
}
// 2.524kb
创建1M goroutine, 平均每个goroutine消耗内存 2.524kb
参考
《Concurrency in Go》