多种阻塞进程方法的消耗比较
工作中遇到一个需要阻塞系统以启动消费者的问题,谷歌到多种阻塞方法,但均未提及资源(cpu
)消耗。所以特将多种方法的消耗进行实验,以期得到一个资源消耗的基本结论。
- 代码
func main() {
wg := sync.WaitGroup{}
go func() {
wg.Add(1)
defer wg.Done()
pid := os.Getpid()
fmt.Printf("子协程 PID: %d \n", pid)
time.Sleep(120 * time.Second)
}()
pid := os.Getpid()
fmt.Printf("主协程 PID: %d \n", pid)
wg.Add(1)
wg.Wait()
}
- 输出
leetcode go build -o leet leetcode.go 503ms
➜ leetcode ./leet 266ms
主协程 PID: 78932
子协程 PID: 78932
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000078f70)
/Users/brick/sdk/go1.17.1/src/runtime/sema.go:56 +0x25
sync.(*WaitGroup).Wait(0x10c1860)
/Users/brick/sdk/go1.17.1/src/sync/waitgroup.go:130 +0x71
main.main()
/Users/brick/personalspace/golearn/leetcode/leetcode.go:52 +0xd7
- 资源消耗
➜ ~ procs leet 154ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
78932 brick │ s001 0.0 0.0 00:00:00 │ ./leet
- 代码
func main() {
go func() {
pid := os.Getpid()
fmt.Printf("子协程 PID: %d \n", pid)
time.Sleep(120 * time.Second)
}()
pid := os.Getpid()
fmt.Printf("主协程 PID: %d \n", pid)
select {}
}
- 输出
➜ leetcode ./leet 8ms
主协程 PID: 83310
子协程 PID: 83310
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
main.main()
/Users/brick/personalspace/golearn/leetcode/leetcode.go:47 +0x79
➜ leetcode
- 资源消耗
➜ ~ procs leet 165ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
83310 brick │ s001 0.0 0.0 00:00:00 │ ./leet
➜ ~
- 代码
func main() {
pid := os.Getpid()
fmt.Printf("主协程 PID: %d \n", pid)
for {
}
}
- 输出
➜ leetcode ./leet 242ms
主协程 PID: 84197
^C
➜ leetcode
- 资源消耗
➜ ~ procs leet 168ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
84197 brick │ s001 105.7 0.0 00:00:10 │ ./leet
➜ ~ procs leet 143ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
84197 brick │ s001 100.0 0.0 00:00:12 │ ./leet
➜ ~ procs leet 142ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
84197 brick │ s001 99.0 0.0 00:00:13 │ ./leet
➜ ~ procs leet 139ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
84197 brick │ s001 96.3 0.0 00:00:14 │ ./leet
➜ ~ procs leet 141ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
84197 brick │ s001 99.1 0.0 00:00:15 │ ./leet
➜ ~
- 代码
func main() {
var m sync.Mutex
go func() {
pid := os.Getpid()
fmt.Printf("子协程 PID: %d \n", pid)
time.Sleep(60 * time.Second)
}()
pid := os.Getpid()
fmt.Printf("主协程 PID: %d \n", pid)
m.Lock()
m.Lock()
}
- 输出
➜ leetcode ./leet 245ms
主协程 PID: 86143
子协程 PID: 86143
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_SemacquireMutex(0x13, 0x60, 0xc000072f10)
/Users/brick/sdk/go1.17.1/src/runtime/sema.go:71 +0x25
sync.(*Mutex).lockSlow(0xc0000160a8)
/Users/brick/sdk/go1.17.1/src/sync/mutex.go:138 +0x165
sync.(*Mutex).Lock(...)
/Users/brick/sdk/go1.17.1/src/sync/mutex.go:81
main.main()
/Users/brick/personalspace/golearn/leetcode/leetcode.go:50 +0xcc
➜ leetcode
- 资源消耗
➜ ~ procs leet 151ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
86143 brick │ s001 0.0 0.0 00:00:00 │ ./leet
➜ ~
- 代码
func main() {
c := make(chan struct{})
go func() {
pid := os.Getpid()
fmt.Printf("子协程 PID: %d \n", pid)
time.Sleep(60 * time.Second)
}()
pid := os.Getpid()
fmt.Printf("主协程 PID: %d \n", pid)
<-c
}
- 输出
➜ leetcode go build -o leet leetcode.go 139ms
➜ leetcode ./leet 249ms
主协程 PID: 87117
子协程 PID: 87117
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/Users/brick/personalspace/golearn/leetcode/leetcode.go:48 +0x95
➜ leetcode
- 资源消耗
➜ ~ procs leet 152ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
87117 brick │ s001 0.0 0.0 00:00:00 │ ./leet
➜ ~
- 代码
func main() {
sig := make(chan os.Signal, 2)
signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
<-sig
}
- 输出
➜ leetcode go build -o leet leetcode.go 542ms
➜ leetcode ./leet 261ms
- 资源消耗
➜ ~ procs leet 149ms
PID:▲ User │ TTY CPU MEM CPU Time │ Command
│ [%] [%] │
89015 brick │ s001 0.0 0.0 00:00:00 │ ./leet
➜ ~
对比上述各种方法,可发现除使用for
循环阻塞系统,cpu
的消耗率可达到100%
外,其余阻塞方法对cpu
的消耗均接近于0
。可见for
是最不经济和最不可取的阻塞方法。其余方法也有局限性,即程序在所有groutine
都阻塞后,会报错all goroutines are asleep - deadlock!
,即需要额外代码维护程序的正常运行。
使用signal
进行阻塞,本质上也是利用chan
在进行阻塞,但不需要额外的操作以避免goroutine
死锁的情况。只针对阻塞系统的目标而言,推荐使用signal
实现。