Go中的Goroutines&Channels | 青训营笔记

24 阅读4分钟

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

引入

Go语言有一个先天优势,即在语言层面上支持并发操作,这也是Go的灵魂。
在Go中,可以使用关键字goroutines来创建一个协程,使用make来创建一个用于通信的通道。下面先来简单介绍一些基本概念。

基本概念

线程(Thread) :有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
协程(coroutine) :又称微线程与子例程(或者称为函数)一样,协程(coroutine)也是一种程序组件。
通信: 线程是操作系统调度的最小单位,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这些线程间可以按照指定的规则共同完成一件任务,所以这些线程之间就需要互相协调,这个过程被称为线程的通信。

一个线程可以运行多个协程。协程比线程更“轻”。在Go语言中,Goroutine可以理解为一种协程。它可以运行在一个或多个线程上。

Goroutines

Goroutines案例

以下案例选自Go官方文档。

// A _goroutine_ is a lightweight thread of execution.

package main

import (
	"fmt"
	"time"
)

func f(from string) {
	for i := 0; i < 3; i++ {
		fmt.Println(from, ":", i)
	}
}

func main() {

	// Suppose we have a function call `f(s)`. Here's how
	// we'd call that in the usual way, running it
	// synchronously.
	f("direct")

	// To invoke this function in a goroutine, use
	// `go f(s)`. This new goroutine will execute
	// concurrently with the calling one.
	go f("goroutine")

	// You can also start a goroutine for an anonymous
	// function call.
	go func(msg string) {
		fmt.Println(msg)
	}("going")

	// Our two function calls are running asynchronously in
	// separate goroutines now. Wait for them to finish
	// (for a more robust approach, use a [WaitGroup](waitgroups)).
	time.Sleep(time.Second)
	fmt.Println("done")
}

在这个例子中,我们使用了go关键字分别创建了两个Goroutines,使得函数f()和另一匿名函数func(msg)并发执行了。 这段程序运行的结果为:

direct : 0
direct : 1
direct : 2
goroutine : 0
going
goroutine : 1
goroutine : 2
done

可以看到f(goroutine)func(going)在交替地输出结果,因为它们被并发地执行了。

Channels

线程通信主要可以分为三种方式,分别为共享内存消息传递管道流。每种方式有不同的方法来实现

  • 共享内存:线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。

  • 消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。

  • 管道流

CSP并发模型于1970年左右提出,这个模型提出:
DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.
“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”
*

于是,Channels就孕育而生。

Channels案例

以下案例选自Go官方文档。

// _Channels_ are the pipes that connect concurrent
// goroutines. You can send values into channels from one
// goroutine and receive those values into another
// goroutine.

package main

import "fmt"

func main() {

	// Create a new channel with `make(chan val-type)`.
	// Channels are typed by the values they convey.
	messages := make(chan string)

	// _Send_ a value into a channel using the `channel <-`
	// syntax. Here we send `"ping"`  to the `messages`
	// channel we made above, from a new goroutine.
	go func() { messages <- "ping" }()

	// The `<-channel` syntax _receives_ a value from the
	// channel. Here we'll receive the `"ping"` message
	// we sent above and print it out.
	msg := <-messages
	fmt.Println(msg)
}

在Go中,我们可以使用make来创建一个通道,使用通道 <- 信息这一语法将信息存入通道中,使用容器 <- 通道这一语法将通道中的内容取出并存放到指定位置处。
因此在上述程序中,消息ping就从一个goroutines通过一个channels传递到了另一个goroutines中了。

使用通道的目的

By default sends and receives block until both the sender and receiver are ready. This property allowed us to wait at the end of our program for the "ping" message without having to use any other synchronization.

以上选自Go官方,即:

默认情况下,发送和接收都会阻塞,直到发送方和接收方都准备好。这个属性允许我们在程序的末尾等待“ping”消息,而不必使用任何其他同步。

如有疏漏,还望海涵。