Golang中的Goroutine,Channel与WaitGroup

Goroutine 提供了一种在 Golang 应用程序中进行多处理的方法,允许多个进程同时运行。此外,Channels 和 WaitGroups 允许在线程之间传递数据或阻塞一个线程直到另一个线程完成。

Goroutines 是 Go 中内置的轻量级多处理线程。它们允许并行执行并发操作,并在调用给定函数时使用go关键字定义。当使用go关键字调用函数时,Go 会自动创建一个单独的线程来执行定义的进程。

但是,默认情况下,无论任何包含的 goroutines 是否完成,主线程都会运行到完成。例如:

1
2
3
4
5
6
7
8
9
10
11
12
func numbers() {
for i := 0; i < 8; i++ {
fmt.Println(i)
}
}

func main() {
fmt.Println("Main starting...")
go numbers()
fmt.Println("Main finished...")

}

输出:

1
2
Main starting...
Main finished...

当不使用go关键字运行相同的代码时,得到以下结果:

1
2
3
4
5
6
7
8
9
10
Main starting...
0
1
2
3
4
5
6
7
Main finished...

包含go关键字后,numbers()函数将在单独的线程中执行;但是,主线程不会等待第二个线程完成。相反,它只是继续完成。

Channel

channel 允许 goroutine 将特定数据类型从一个线程传递到另一个线程。channel和它们可以处理的数据类型必须在使用前定义。例如,以下channel定义将接受一个整数:

1
ch := make(chan int)

将数据传递到channel和处理来自channel的数据的语法如下:

1
2
3
ch <- 10
a := <-ch
fmt.fmt.Println(a) // 10

数据沿箭头方向流动,首先进入通道ch,然后从ch流出进入变量a。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func numbers(ch chan int) {
for i := 0; i < 8; i++ {
ch <- i
}
close(ch)
}

func main() {
ch := make(chan int)
fmt.Println("Main starting...")
go numbers(ch)
res := <-ch
fmt.Println(res)
fmt.Println("Main finished...")
}

输出:

1
2
3
Main starting...
0
Main finished...

默认情况下,一个channel一次只能管理一个值,直到这个值从通道中移除,线程才会被阻塞。因为在上面的代码中只从ch中删除一个值,所以只打印从numbers()返回的第一项。

要访问numbers()函数生成的所有值,需要在将值添加到channel时对其进行处理,以便可以依次添加后续值。

在main()函数中添加一个for循环,检查channel中是否有新值,如果有,则处理它。否则循环结束,主线程继续执行直到完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func numbers(ch chan int) {
for i := 0; i < 8; i++ {
ch <- i
}
close(ch)
}

func main() {
ch := make(chan int)
fmt.Println("Main starting...")
go numbers(ch)

for {
res, ok := <-ch
if !ok {
break
}
fmt.Println(res)
}
fmt.Println("Main finished...")
}

输出:

1
2
3
4
5
6
7
8
9
10
Main starting...
0
1
2
3
4
5
6
7
Main finished...

WaitGroup

WaitGroup 是另一种允许其他线程在主线程运行完成之前完成其进程的方法。它们的工作方式是阻塞主线程,直到与 WaitGroup 关联的 goroutine 完成。

在 main 函数中创建一个 WaitGroup 并将其传递给 goroutine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func numbers(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 8; i++ {
fmt.Println(i)
}
}

func main() {
fmt.Println("Main starting...")
wg := new(sync.WaitGroup)
wg.Add(1)
go numbers(wg)
wg.Wait()
fmt.Println("Main finished...")
}

输出:

1
2
3
4
5
6
7
8
9
10
Main starting...
0
1
2
3
4
5
6
7
Main finished...

虽然输出看起来一样,但实际上代码在这里的行为与上面的非常不同。在上面的示例中,channel允许主线程在运行时从第二个线程访问数据,并且是主线程打印输出。

在WaitGroup中,主线程只是被阻塞,直到第二个线程(在调用go numbers()函数时创建)完成其进程。一旦第二个线程处理完成——即打印所有正在生成的数字——主线程就可以继续了。

Goroutine 提供了一种在 Golang 应用程序中进行多处理的方法。将go关键字添加到函数调用时,会创建第二个线程,函数的进程在其中运行。这允许多个进程同时运行。默认情况下,无论任何其他线程的状态如何,主线程都将继续完成。但是,Golang 同时提供 Channel 和 WaitGroup 以便将数据从一个线程传递到另一个线程,或者简单地阻塞一个线程直到另一个线程完成。