Go by Example - Goroutines, Channels, Channel Buffering, Channel Synchronization and Channel Directions

Go by Example - Goroutines, Channels, Channel Buffering, Channel Synchronization and Channel Directions

介绍go中的协程, 通道, 通道缓冲, 通道同步和通道方向

Goroutines

goroutine 是一种轻量级的执行线程。

goroutines.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"time"
)

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

func main() {
// 假设我们有一个函数调用 f(s)
// 下面是我们如何以通常的方式调用它,同步运行它
f("direct")

// 要在一个 goroutine 中调用该函数,请使用 go f(s)
// 新的 goroutine 将与调用的 goroutine 同时执行
go f("goroutine")

// 您也可以为匿名函数调用启动一个 goroutine
go func(msg string) {
fmt.Println(msg)
}("going")

// 我们的两个函数调用现在在单独的goroutine中异步运行
// 等待它们完成(对于更健壮的方法,请使用WaitGroup: https://gobyexample.com/waitgroups)
time.Sleep(time.Second)
fmt.Println("done")
}

当我们运行这个程序时,我们首先看到阻塞调用的输出,然后看到两个goroutine的输出。Goroutine的输出可以交错,因为Go运行时同时运行goroutine。

log
1
2
3
4
5
6
7
8
9
$ go run goroutines.go
direct : 0
direct : 1
direct : 2
goroutine : 0
going
goroutine : 1
goroutine : 2
done

接下来,我们将研究并发Go程序中goroutine的补充:通道。

Channels

通道是连接并发goroutine的管道。您可以将值从一个goroutine发送到通道,并将这些值接收到另一个Gooutine。

channels.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
// 使用make(chan val-type)创建新通道。通道由它们传递的值键入
messages := make(chan string)

// 使用通道<-语法将值发送到通道中
// 在这里,我们从一个新的goroutine向上面创建的消息通道发送”ping“
go func() { messages <- "ping" }()

// <-channel语法从通道接收值
// 在这里,我们将接收上面发送的"ping"消息,并将其打印出来
msg := <-messages
fmt.Println(msg)
}

当我们运行程序时,“ping”消息通过我们的通道成功地从一个goroutine传递到另一个。

log
1
2
$ go run channels.go
ping

默认情况下,发送和接收块,直到发送方和接收方都准备就绪。该属性允许我们在程序结束时等待“ping”消息,而不必使用任何其他同步。

Channel Buffering

默认情况下,通道是无缓冲的,这意味着如果有相应的接收(<-chan)准备接收发送的值,它们将仅接受发送(chan<-)。缓冲通道接受有限数量的值,而没有相应的接收器来接收这些值。

channel-buffering.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
// 这里,我们创建一个字符串通道,最多缓冲2个值
messages := make(chan string, 2)

// 因为该通道是缓冲的,所以我们可以将这些值发送到通道中,而无需相应的并发接收
messages <- "buffered"
messages <- "channel"

// 之后,我们可以像往常一样接收这两个值
fmt.Println(<-messages)
fmt.Println(<-messages)
}
log
1
2
3
$ go run channel-buffering.go
buffered
channel

Channel Synchronization

我们可以使用通道在goroutine之间同步执行。下面是一个使用阻塞接收来等待goroutine完成的示例。当等待多个goroutine完成时,您可能更喜欢使用WaitGroup。

channel-synchronization.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"time"
)

// 这是我们将在goroutine中运行的函数
// done通道将用于通知另一个goroutine该函数的工作已完成
func worker(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done")

// 发送一个值,通知我们已经完成
done <- true
}

func main() {
// 启动 worker goroutine,为其提供通知通道
done := make(chan bool, 1)
go worker(done)

// 阻塞,直到我们收到来自通道上 Worker 的通知
<-done
}
log
1
2
$ go run channel-synchronization.go
working...done

如果删除该程序中的 <- done 行,程序会在 Worker 开始之前退出。

Channel Directions

在使用通道作为函数参数时,可以指定通道是只用于发送还是接收数值。这种特殊性提高了程序的类型安全性。

channel-directions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

// 该 ping 函数只接受一个用于发送值的通道
// 如果尝试在此通道上接收,则会在编译时出错
func ping(pings chan<- string, msg string) {
pings <- msg
}

// pong 函数接受一个用于接收(ping)的通道和另一个用于发送(pongs)的通道
func pong(pings <-chan string, pongs chan<- string) {
msg := <-pings
pongs <- msg
}

func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
log
1
2
$ go run channel-directions.go
passed message

参考链接

Go by Example - Goroutines, Channels, Channel Buffering, Channel Synchronization and Channel Directions

https://blog.wty.cool/2024/03/02/go_by_example/Goroutines-Channels-Channel_Buffering-Channel_Synchronization-Channel_Directions/

作者

孤独小狼

发布于

2024-03-02

更新于

2024-03-02

许可协议

评论