GO语言进阶——Goroutine| 青训营笔记

73 阅读3分钟

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

GO语言进阶

理论基础

进程/线程

进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。
线程是进程的一个执行实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。 一个进程可以创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行。

并发/并行

多线程程序在单核心的 cpu 上运行,称为并发;多线程程序在多核心的 cpu 上运行,称为并行。
并发与并行并不相同,并发主要由切换时间片来实现“同时”运行,并行则是直接利用多核实现多线程的运行,go语言可以充分发挥多核优势高效运行,以发挥多核计算机的能力。
image.png

协程/线程

协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
线程:协程可以理解为轻量级线程,一个线程能够并发运行多个协程 image.png

GO语言协程

使用 go 关键字就可以创建 goroutine,将 go 声明放到一个需调用的函数之前,在相同地址空间调用运行这个函数,这样该函数执行时便会作为一个独立的并发线程,这种线程在Go语言中则被称为 goroutine。

func hello(i int) {
   fmt.Println("hello")
}
func main() {
   for i := 0; i < 5; i++ {
      go func(j int) {
         hello(j)
      }(i)
   }
   time.Sleep(time.Second)
}

goroutine 间的通信

协程间通信由go提供的channel实现,channel可以用make创建

//make(chan 元素类型,[缓冲大小])
make(chan int,5)
make(chan int)

一个例子:

func main() {
   CalSquare()
}
func CalSquare() {
   src := make(chan int)
   dest := make(chan int)

   go func() {//A
      defer close(src)
      for i := 0; i < 10; i++ {
         src <- i
      }
   }()
   go func() {//B
      defer close(dest)
      for i := range src {
         dest <- i * i
      }
   }()
   for i := range dest {
      fmt.Println(i)
   }
}

这段代码实现A作为生产者,通过src chan向B传输数据,在B中遍历src处理数据,通过带缓冲的dest chan返回主函数中操作。

等待组WaitGroup

在第一个例子中,由于无法知道协程是否执行完毕,所以调用time.sleep阻塞。go提供了WaitGroup实现同步: image.png 每一个任务开始时, 将等待组增加一,一个任务完成时,等待组减一,wait()可以阻塞函数直到等待组计数为零

小结

本节课程主要讲解了GO语言进阶,包括Goroutine、Channel和Sync。其中难点是关于channel通信,通过课外学习,我还了解了以下内容:

通道接收数据方式

1、阻塞接受数据

data := <-ch

执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量

2、非阻塞接收数据

data, ok := <-ch
  • data:表示接收到的数据。未接收到数据时,data 为通道类型的零值。
  • ok:表示是否接收到数据。

3、接收任意数据,忽略接收的数据

<-ch

执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。这个方式实际上只是通过通道在 goroutine 间阻塞收发实现并发同步。

4、循环接收

for data := range ch {
    //code
}

需要注意的是,由于非阻塞的通道接收方法可能造成高的 CPU 占用,因此第三种方式使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel 进行。