1、进程、线程和协程的不同
进程:进程是具有一定独立功能的程序,进程是系统资源分配和调度的最小单元。每个进程都有自己独立的内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立内存,所以上下文进程间的切换开销比较大(寄存器、虚拟内存、文件句柄等),但相对比较稳定安全。
线程:线程是进程的一个实体,线程是内核态,而且是CPU调度和分派的基本单位,它是比进程更小的独立单元。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定,容易丢失数据。一个进程可以有多个线程,同一个进程内的线程共享了堆内存,所以经常会引起编发编程问题。
协程:协程是一种用户态的轻量级线程,协程的调度完全是由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文切换非常快。
2、为什么要引入协程?
协程的引入,本质上是为了规避一个问题:我又想有并发,又不想陷入到内核里面去,于是有了协程这个东西。
协程是轻量级的线程,主要体现在两个方面:
1、所需的资源更少。
2、创建、销毁和调度更轻量,并不需要陷入内核。
随着计算机性能提高、业务的发展,要求我们有更高的并发,而大多数并发执行的任务都是短平快,单独一个线程划不来,即时使用线程池,也会带来频繁切换上下文、陷入内核的问题。
因此,我们需要更轻量的东西取代线程。
3、goroutine泄露典型场景
参考煎鱼大佬:
跟读者聊 Goroutine 泄露的 N 种方法,真刺激!_煎鱼(EDDYCJY)的博客-CSDN博客
排场主要用 rumtine.NumGoroutine 或者 pprof 工具,pprof 会返回所有带有堆栈跟踪的 goroutine 列表。
4、怎么避免 goroutine 泄露
如果你不知道 goroutine 什么时候会结束,就不要使用 goroutine , 则是核心原则。
防止 goroutine 泄露可以由以下两个方向:
1、超时控制,使用context.Timeout的特性。
2、信号通知,主动发送信号给goroutine关闭,一般是要使用channel的特性。
这两个方向,都离不开select来配合,要么是业务正常结束,退出 goroutine,要么是超时,或者收到关闭信号,异常退出。
如:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
bsChan := make(chan struct{})
go func() {
slowBusiness()
bsChan <- struct{}{}
}()
select {
case <-ctx.Done():
fmt.Println("timeout")
case <-bsChan:
fmt.Println("business end")
}
}
func slowBusiness() {
time.Sleep(2 * time.Second)
}
5、Mutext 加锁
mutex加锁大概分成两种模式:
1、正常模式下,goroutine会通过自旋来获得锁;
2、但是如果存在一个 goroutine 等待锁超过 1ms,那么 mutex 就会进入饥饿模式,在饥饿模式下,会遵循 FIFO 原则,将锁交给下一个 goroutine。也就是通过 P 等待队列队里里面唤醒第一个等待者。
6、什么是饥饿模式
mutex有两种模式 — 正常模式和饥饿模式。
在正常模式下,锁的等待者会按照先进先出的顺序获取锁。但是刚被唤起的 Goroutine 与新创建的 Goroutine 竞争时,大概率会获取不到锁,为了减少这种情况的出现,一旦 Goroutine 超过 1ms 没有获取到锁,它就会将当前互斥锁切换饥饿模式,防止部分 Goroutine 被“饿死”。
在饥饿模式中,互斥锁会直接交给等待队列最前面的 Goroutine。新的 Goroutine 在该状态下不能获取锁、也不会进入自旋状态,它们只会在队列的末尾等待。如果一个 Goroutine 获得了互斥锁并且它在队列的末尾或者它等待的时间少于 1ms,那么当前的互斥锁就会切换回正常模式。
与饥饿模式相比,正常模式下的互斥锁能够提供更好地性能,饥饿模式的能避免 Goroutine 由于陷入等待无法获取锁而造成的高尾延时。
以上规则是由1.9更新。
使用Go 1.8 循环了10次进行测试。
package main
import (
"sync"
"time"
)
func main() {
done := make(chan bool, 1)
var mu sync.Mutex
// goroutine 1
go func() {
for {
select {
case <-done:
return
default:
mu.Lock()
time.Sleep(100 * time.Microsecond)
mu.Unlock()
}
}
}()
// goroutine2
for i := 0; i < 10; i++ {
time.Sleep(100 * time.Microsecond)
mu.Lock()
mu.Unlock()
}
done <- true
}
--摘自:《Go语言设计与实现》
7、GMP模型
参考大佬的文章: