1. Goroutine的概念
在Golang(也称为Go语言)中,Goroutine是一种轻量级线程,它可以在同一进程中并发运行。与传统线程相比,Goroutine的创建、销毁和切换的成本非常小,因此可以创建数以千计的Goroutine。
我们可以使用关键字go来启动一个新的Goroutine。下面是一个简单的示例:
func main(){
go hello()
time.Sleep(time.Second) //让当前Goroutine暂停1秒钟
}
func hello(){
fmt.Println("Hello Goroutine!")
}
1.1 Goroutine的优势
与传统线程模型相比,Goroutine具有以下几个优势:
成本较低:在创建、销毁和切换上的开销非常小,因此可以创建数千个或数十万个Goroutine。
更高效的调度:Golang的调度器能够在多个线程之间智能地分配时间片,从而最大化地利用系统资源。
更强大的上下文切换:在传统线程模型中,上下文切换的开销很大。但在Golang中,每个Goroutine都只需要在堆栈上保存它的上下文,因此上下文切换的成本非常小。
2. Goroutine的高级用法
2.1 理解channel
在Golang中,channel是一种同步的原语,用于在Goroutine之间进行通信。它可以将数据从一个Goroutine发送到另一个Goroutine,并且可以保证在接收到数据之前,发送方一直阻塞。
channel有两种类型:带缓冲和不带缓冲。带缓冲的channel可以在发送时将数据存储在缓冲区中,而不是直接将数据发送给接收方。这使得发送方可以不被阻塞,直到缓冲区满为止。
2.2 使用channel实现并发
以下是一个使用channel实现并发的示例,它展示了如何使用Goroutine和channel协调两个任务:
var done = make(chan bool)
func main() {
go worker()
<-done //等待worker()执行完毕
}
func worker(){
fmt.Println("Worker started.")
time.Sleep(time.Second)
fmt.Println("Worker done.")
done <- true //发送完成信号
}
在上面的示例中,main()函数启动了一个新的Goroutine来执行worker()函数。在worker()函数中,它会打印一条消息并休眠1秒钟。然后,它会发送一个表示任务已经完成的信号给done channel。
在main()函数中,<-done代码用于等待worker()函数完成。当worker()函数完成时,它会向done channel发送完成信号。接下来,<-done代码从done channel中接收完成信号并继续执行main()函数。
2.3 使用sync.WaitGroup等待Goroutine完成
在Golang中,sync.WaitGroup是一个用于等待一组Goroutine完成的原语。它允许我们等待一组Goroutine完成,并在它们全部完成后继续执行。
以下是一个示例,展示了如何使用sync.WaitGroup等待多个Goroutine完成:
func main(){
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Println("Goroutine #",n," started")
time.Sleep(time.Second)
fmt.Println("Goroutine #",n," done")
}(i)
}
wg.Wait()
}
在上面的示例中,我们创建了5个Goroutine,并使用sync.WaitGroup来等待它们全部完成。在每个Goroutine中,我们使用wg.Add(1)将计数器+1,标识当前有一个Goroutine正在运行。在Goroutine完成后,我们使用wg.Done()将计数器-1,标识当前Goroutine已经完成。
3. 总结
在本文中,我们深入探讨了Golang中Goroutine的概念,并介绍了Goroutine的优势。我们还讨论了如何使用channel和sync.WaitGroup等技术来协调多个Goroutine之间的并发执行,从而最大化地利用系统资源。