Go语言进阶 | 青训营笔记

134 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

内容源于青训营课堂视频以及一些go文档和自己的经验、理解,若有错误欢迎及时指出

1.1 Goroutine

goroutine机制类似于线程,但是在运行时调度和管理的,程序会智能地将goroutine中的任务合理分配给CPU,在语言层面内置了调度和上下文切换的机制

协程:用户态,轻量级线程,栈MB级

线程:内核态,线程跑多个协程,栈KB级

使用goroutine

启动协程使用go关键字即可,一个goroutine一定对应一个函数,一个函数可以由多个goroutine执行

func main() {
   for i := 0; i < 100; i++ {
      go func(i int) {
         fmt.Println(i) //此时用的是函数外面的i
      }(i) //闭包函数的话
   }
   fmt.Println("main")
   time.Sleep(time.Second)
}

可增长的栈

OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB,虽然极少会用到这么大。所以在Go语言中一次创建十万左右的goroutine也是可以的。

goroutine调度机制

GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。

  • G goroutine 每个go关键字都会创建一个协程,里面有与所在P的绑定等信息
  • P processor 管理着一组G队列,并进行调度(比如把占用CPU时间较长的G暂停、运行后续的G等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
  • M machine 是GO运行时对操作系统内核线程的虚拟,M与内核线程是一一映射的关系,G都要放到M上才能运行

单从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的,goroutine则是由Go运行时(runtime)自己的调度器调度的,这个调度器使用一个称为m:n调度的技术(复用/调度m个goroutine到n个OS线程)。

1.2CSP

Communicating Sequential Processes

Go语言提倡通过通信共享内存而不是通过共享内存而实现通信

  • 通过通信共享内存
    • 使用channel实现,类似消息队列
  • 共享内存实现通信
    • 临界区加锁,影响性能

1.3Channel

make关键字

  • 无缓冲(同步) make (chan int)
  • 有缓冲 make (chan int, 2)

通道的操作

箭头代表的是数据流向

  • 发送值
    ch1 <- 1

  • 接受值
    x := <- ch1 给x赋值
    <- ch1 直接丢掉了

  • 关闭
    close(ch1) 关闭一个未初始化的或已经关闭的channel会引发panic

1.4并发安全Lock

  • mutex互斥锁
var x = 0
var wg sync.WaitGroup
var lock sync.Mutex
 
func add() {
   lock.Lock()
   for i := 0; i < 500000; i++ {
      //lock.Lock()
      x = x + 1
      //lock.Unlock()
   }
   lock.Unlock()
   wg.Done()
}
func main() {
   wg.Add(3)
   go add()
   go add()
   go add()
   //使用了公共空间导致同时写入的时候部分丢失了,少于150000
   //协程越多,重复写入可能性越大,差距越大
   wg.Wait()
   fmt.Println(x)
}

使用了公共空间导致同时写入的时候部分丢失了,少于150000 协程越多,重复写入可能性越大,差距越大

  • rwLock 读写互斥锁 当读的操作数量远大于写操作的时候,读写互斥锁效率高 读的时候别人不需要等待,只有写的时候要等
var (
   x = 0
   //lock   sync.Mutex
   wg     sync.WaitGroup
   rwLock sync.RWMutex
)

1.5WaitGroup

var wg sync.WaitGroup

func main() {
   for i := 0; i < 10; i++ {
      wg.Add(1) //计数
      go f1(i)
   } //如何知道这10个goroutine都执行结束了?
   
   wg.Wait() //等待wg的计数器减为0
}