一、Goroutine
1. 什么是 Goroutine?
Goroutine 是 Go 语言中执行并发任务的基本单位。它是一种轻量级线程,由 Go 运行时管理。启动一个新的 goroutine 只需使用 go 关键字。
2. 使用示例
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello from goroutine!")
}
func main() {
go sayHello() // 启动一个新的 goroutine
time.Sleep(1 * time.Second) // 等待 goroutine 完成
}
在上面的示例中,我们启动了一个新的goroutine,由于主函数在 goroutine 启动后立即结束,因此可以使用 time.Sleep 函数来保证主程序等待足够的时间,以便 goroutine 完成所分配的任务。但是这种做法并不建议,因为我们并不能知道一个任务的完成究竟需要多长时间,更好的做法是,当任务完成时,通知主程序任务已完成。
二、Channel
1. 什么是 Channel?
Channel 是 Go 中用于在 goroutine 之间进行通信的管道。它允许一个 goroutine 发送数据到另一个 goroutine,并确保数据的安全传输。
2. 如何创建 Channel
可以使用 make 函数创建一个 channel:
ch := make(chan int)
3. 发送和接收数据
在 channel 中发送和接收数据的基本语法如下:
// 发送数据
ch <- value
// 接收数据
value := <-ch
4. 如何使用 Channel
package main
import (
"fmt"
)
func calculateSquare(numbers []int, ch chan int) {
for _, n := range numbers {
ch <- n * n // 发送结果到 channel
}
close(ch) // 关闭 channel
}
func main() {
numbers := []int{1, 2, 3, 4, 5}
ch := make(chan int)
go calculateSquare(numbers, ch) // 启动 goroutine
for result := range ch { // 从 channel 接收结果
fmt.Println(result)
}
}
在上面的示例中,我们创建了一个goroutin来执行 calculateSquare 函数, calculateSquare 函数计算每个数字的平方,并将结果发送到channel中。主函数从 channel 中接收结果并打印,直到 channel 关闭。
for result := range ch { // 从 channel 接收结果
fmt.Println(result)
}
这里的写法非常适合从channel中接收数据,在channel未关闭前,for循环可以不断的从channel中取出元素并使用。当channel关闭之后,for循环也会自动结束(在取出channel中的所有元素之后),避免了手动检查 channel 的需要。
三、Buffered Channel
1. 什么是 Buffered Channel?
Buffered channel 是一种具有缓冲区的 channel,可以在没有 goroutine 等待接收的情况下发送多个值。可以通过指定缓冲区大小来创建它:
ch := make(chan int, 3) // 创建一个缓冲区大小为 3 的 channel
对于一个没有指定缓冲区的channel来说,如果没有其他操作来接收channel中的元素,那么将元素发送到channel的行为就会被阻塞,直到channel中的元素被取走为止。
这也可以理解为容量为1的channel。
而具有缓冲区的channel则可以接收多个元素,直到缓冲区满了之后,同样被阻塞。
2. 如何使用 Buffered Channel
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 2) // 创建一个缓冲区为 2 的 channel
ch <- 1
ch <- 2
fmt.Println(<-ch) // 接收数据
fmt.Println(<-ch)
}
只需要在创建的时候指定channel的大小就可以使用带缓冲区的channel了。
四、Select 语句
1. 什么是 Select?
select 语句用于处理多个 channel 操作。允许等待多个 channel 的操作,一旦其中一个 channel 准备好,就会执行相应的 case。
2. 如何使用 Select
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "result from ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "result from ch2"
}()
select {
case res := <-ch1:
fmt.Println(res)
case res := <-ch2:
fmt.Println(res)
}
}
在上面的示例中,主函数使用 select 等待来自 ch1 和 ch2 的结果,并在其中一个 channel 准备好时打印结果。
在上面的示例中,我并没有使用默认分支,也就是default: 语句。这会导致select的一直等待,直到其中一个channel中被输入元素为止。如果设置了默认分支,在其他分支都阻塞的情况下,会直接执行默认分支。
select语句在执行分支时的判断是从上到下的,从而判断各个分支是否满足条件。如果select语句发现有多个分支都满足条件,那么会使用随机算法挑选其中的一个分支执行