goroutine

在 Go 语言中,可以使用 goroutine 来实现轻量级的并发。当一个 Go 程序运行时,调用 main 函数入口的 goroutine 是主 goroutine。我们可以使用关键字 go 创建新的 goroutine。创建 goroutine 的语法很简单,只需要在函数或方法调用前加上 go 关键字。

func printHello() {
	fmt.Println("Hello")
}

func main() {
	go printHello()
	time.Sleep(time.Second)
	fmt.Println("Main goroutine")
}

// 输出结果:
// Main goroutine
// Hello

channel

channel 是用于不同 goroutine 之间进行通信的机制。Go 语言倡导使用 CSP(Communicating Sequential Processes)并发编程风格,即通过通信来共享内存,以实现并发安全。

和 map 类似,channel 也可以通过内置函数 make 来创建。channel 是引用类型,其零值是 nil,可以与 nil 进行比较。同类型的 channel 之间可以使用"==“运算符进行比较。

创建 channel 时,可指定元素类型作为第一个参数,可选参数作为第二个参数表示 channel 的容量。未设置容量的 channel 称为无缓冲 channel,设置了容量的 channel 称为缓冲 channel。无缓冲 channel 是一种阻塞的同步 channel。通过内置函数 cap 可以获取缓冲 channel 的容量,len 可以获取缓冲 channel 中元素的个数。

func main() {
	// 创建一个无缓冲channel
	ch := make(chan int)

	go func() {
		ch <- 10
	}()

	x := <-ch
	fmt.Println(x)
}

// 输出结果:
// 10

channel 操作

channel 的操作主要有 send、receive 和 close 三种。send 操作用于一个 goroutine 向另一个 goroutine 传输值,receive 操作用于接收 goroutine 发送的值,close 操作用于关闭 channel。

send 操作和 receive 操作都使用”<-“操作符。在 send 语句中,channel 和值分别位于操作符的左右两边;在 receive 语句中,操作符位于 channel 操作数的前面。

关闭 channel 可以通过使用 close 来表示当前值已经发送完毕,之后的 send 操作将导致 panic。在已关闭的 channel 上执行 receive 操作,将获取该 channel 上已经发送的所有值,直到 channel 为空。

对于缓冲 channel,send 操作在队列尾部插入一个元素,receive 操作在队列头部移除一个元素。当缓冲 channel 的容量占满时,send 操作会阻塞发送 goroutine,直到另一个 goroutine 执行 receive 操作并腾出空间。如果缓冲 channel 为空,执行 receive 操作的 goroutine 会阻塞,直到另一个 goroutine 在该缓冲 channel 上发送数据。

需要注意的是,如果在同一个缓冲 channel 上执行 send 操作和 receive 操作,相当于错误地将缓冲 channel 作为队列使用。在实际项目开发中,一般会由不同的 goroutine 执行 send 操作和 receive 操作。

无缓冲 channel 中的每次 send 操作都对应一次 receive 操作,实现强同步。而缓冲 channel 中的 send 操作和 receive 操作是解耦的。如果事先知道发送值的数量上限,可以创建一个具有指定容量的缓冲 channel,在接收第一个值之前将所有值发送完毕。

使用缓冲 channel 时需要注意发送和接收两个 goroutine 的执行效率,以免影响性能。

func main() {
	ch := make(chan int, 1)
	ch <- 10 // 发送值到channel

	x := <-ch // 从channel接收值
	fmt.Println(x)

	close(ch) // 关闭channel
}
// 输出结果:
// 10

select 多路复用

select 语句类似于 switch 语句,它可用于处理多个通信操作。select 语句包含多个 case 分支和一个可选的默认分支 default。每个 case 分支指定一次通信操作(发送或接收操作),以及与之关联的一段代码块。

select 语句一直处于等待状态,直到某个 case 分支的通信操作可以执行并满足条件,然后执行该分支的代码块。其他通信操作不会发生。如果没有任何 case 分支满足条件,select 语句将永远等待。如果有多个 case 分支同时满足条件,select 语句将随机选择一个分支执行。如果存在默认分支 default,在所有 case 分支的通信操作都不能执行时,将执行默认分支的代码块。

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		time.Sleep(time.Second)
		ch1 <- 10
	}()

	go func() {
		time.Sleep(2 * time.Second)
		ch2 <- 20
	}()

	select {
	case x := <-ch1:
		fmt.Println("Received from ch1:", x)
	case x := <-ch2:
		fmt.Println("Received from ch2:", x)
	default:
		fmt.Println("No goroutine ready")
	}
}

// 输出结果:
// No goroutine ready