Go 语言入门指南:并发语法基础 | 豆包MarsCode AI刷题

29 阅读4分钟

一、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 等待来自 ch1ch2 的结果,并在其中一个 channel 准备好时打印结果。

在上面的示例中,我并没有使用默认分支,也就是default: 语句。这会导致select的一直等待,直到其中一个channel中被输入元素为止。如果设置了默认分支,在其他分支都阻塞的情况下,会直接执行默认分支。

select语句在执行分支时的判断是从上到下的,从而判断各个分支是否满足条件。如果select语句发现有多个分支都满足条件,那么会使用随机算法挑选其中的一个分支执行