如何暂停当前 Goroutine 的执行?
在 Go 语言中,Goroutine 是一种轻量级的线程,可以在一个程序里同时执行多个并发任务,并将代码在多个 CPU 上自动分配。但是,在有些场景下,我们需要暂停 Goroutine 的执行,如等待其他 Goroutine 完成任务后再继续执行,或者需要在一段时间后再继续执行等操作。那么,如何暂停当前 Goroutine 的执行呢?下面,我们来了解几种常用的方式。
1. time.Sleep()
time.Sleep()
函数是一种简单、易用的暂停 Goroutine 的方式。
例如,在下面的示例中,我们定义一个名为 longRunningTask()
的函数,该函数会休眠 5 秒钟并返回结果。我们调用该函数时,使用 time.Sleep()
函数暂停 Goroutine 的执行 1 秒钟,使得程序有时间将其他 Goroutine 运行起来。然后,再继续执行当前 Goroutine。
package main
import (
"fmt"
"time"
)
func longRunningTask() string {
time.Sleep(5 * time.Second)
return "任务完成"
}
func main() {
fmt.Println("开始执行任务...")
go fmt.Println(longRunningTask())
time.Sleep(1 * time.Second) // 等待1秒钟
fmt.Println("任务已启动")
}
输出结果:
开始执行任务...
任务已启动
任务完成
正如上面所说,time.Sleep()
函数是一个简单易用的暂停 Goroutine 的方式,但是它也有一个缺点:在休眠的时间内无论何时都无法执行其他任务。因此,如果我们要上述示例中的长时间任务运行时间更长,比如 10 秒钟或更长,那么在等待这段时间过程中我们的程序就无法执行任何任务。
2. 利用通道等待
Go 语言的通道可以让我们实现 Goroutine 的同步。通过使用通道,我们可以等待一个 Goroutine 完成它的任务,并在需要时再通过通道通知其他 Goroutine 继续执行,从而实现暂停当前 Goroutine 的执行。
例如,在下面的示例中,我们定义一个名为 longRunningTask()
的函数,该函数会休眠 5 秒钟,并向通道 ch
发送消息。然后,我们使用程序中的 <-ch
代码行来暂停当前 Goroutine 的执行,并等待 longRunningTask()
函数通过通道 ch
发送消息,通知当前 Goroutine 继续执行。当 longRunningTask()
函数发送消息后,当前 Goroutine 再继续执行。
package main
import (
"fmt"
"time"
)
func longRunningTask(ch chan string) string {
time.Sleep(5 * time.Second)
ch <- "任务完成"
return "任务完成"
}
func main() {
fmt.Println("开始执行任务...")
ch := make(chan string)
go fmt.Println(longRunningTask(ch))
fmt.Println(<-ch) // 暂停当前goroutine的执行并等待longRunningTask()从通道ch发送消息。
fmt.Println("任务已启动")
}
输出结果:
开始执行任务...
任务完成
任务已启动
如上所述,我们使用通道等待了 longRunningTask()
函数完成,并在需要时通过通道发送消息,通知 Goroutine 继续执行。但是,通过该方式需要手动创建和使用通道,比较麻烦和繁琐,特别是当需要同时等待一组并发完成任务的 Goroutine 时,这种方式就显得非常麻烦。
3. 利用 sync.WaitGroup 等待
Go 语言提供了一个名为 sync.WaitGroup
的同步结构体,它可以方便地等待一组 Goroutine 完成,而不需要使用复杂的通道等待。
例如,在下面的示例中,我们定义一个名为 longRunningTask()
的函数,该函数会休眠 5 秒钟并返回结果。然后,我们使用 sync.WaitGroup
结构体等待它完成,并向程序中的 wg.Done()
函数添加完成信号。在主函数中,我们调用 wg.Wait()
函数暂停当前 Goroutine 的执行,并等待 wg.Done()
函数收到完成信号,通知当前 Goroutine 继续执行。
package main
import (
"fmt"
"sync"
"time"
)
func longRunningTask(wg *sync.WaitGroup) string {
defer wg.Done()
time.Sleep(5 * time.Second)
return "任务完成"
}
func main() {
fmt.Println("开始执行任务...")
var wg sync.WaitGroup
wg.Add(1)
go fmt.Println(longRunningTask(&wg))
wg.Wait() // 暂停当前 Goroutine 的执行,并等待 go 函数执行结束
fmt.Println("任务已启动")
}
输出结果:
开始执行任务...
任务完成
任务已启动
在上述示例中,我们使用 sync.WaitGroup
等待了 longRunningTask()
函数执行结束,并通过 wg.Done()
函数通知其它 Goroutine 继续执行。通过使用 sync.WaitGroup
,我们可以方便的等待多个 Goroutine 完成并同时继续执行。此外,sync.WaitGroup
还提供了一些其他的方法来简化多个 Goroutine 同步的操作。
结论
以上三种方式都可以暂停当前 Goroutine 的执行,但使用不同的方式和场合适合。time.Sleep()
适合轻量级的场景,但需要注意,过长的休眠时间可能导致程序的性能变差。通道对于等待较长时间的 Goroutine 很有用。而且,利用 sync.WaitGroup
可以更方便地等待多个 Goroutine 完成并在所有 Goroutine 都完成后继续执行。
总之,在选择暂停 Goroutine 执行时,我们需要根据实际的场景和需要进行选择。这样可以使我们更好的利用 Go 语言的并发性能,并在性能和实现的复杂度之间达到一个平衡点。