Go语言的并发编程通过**goroutine
和channel
来实现。goroutine
是一种轻量级线程,而channel
则是Go提供的用于goroutine之间通信**的工具。
1. goroutine
的使用
**goroutine
**是一种并发执行的函数。使用go
关键字可以将一个函数或方法运行在一个新的goroutine
中,而不阻塞当前的执行。
示例 1:启动一个简单的goroutine
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello, Go!")
}
func main() {
go sayHello() // 启动一个新的 goroutine
time.Sleep(time.Second) // 主 goroutine 暂停 1 秒,以确保 sayHello 有时间运行
fmt.Println("Main function done")
}
time.Nanosecond:1纳秒
time.Microsecond:1微秒
time.Millisecond:1毫秒
time.Second:1秒
time.Minute:1分钟
time.Hour:1小时
在这个例子中:
go sayHello()
在一个新的goroutine
中运行。main
函数暂停1秒钟,以确保sayHello
有足够的时间完成。因为goroutine
是异步执行的,所以不等待它结束就会继续执行主程序。
示例 2:在主函数中等待多个goroutine
package main
import (
"fmt"
"sync"
)
func printNumber(id int, wg *sync.WaitGroup) {
defer wg.Done() //使用 defer wg.Done() 确保在 goroutine 结束时调用 Done() 方法,减少 WaitGroup 的计数器。
fmt.Printf("Goroutine %d is running\n", id)
}
func main() {
var wg sync.WaitGroup // 使用 WaitGroup 等待所有 goroutine 完成
for i := 1; i <= 5; i++ {
wg.Add(1) // 每启动一个 goroutine 就增加计数
go printNumber(i, &wg)
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println("All goroutines completed")
}
在这个例子中:
sync.WaitGroup
用于等待多个goroutine
完成。wg.Add(1)
在每个goroutine
启动时增加计数,defer wg.Done()
在每个goroutine
结束时减少计数。wg.Wait()
阻塞主goroutine
,直到所有计数都归零。
2. channel
的使用
**channel
**是Go中的通信机制,允许多个goroutine
间安全地传递数据。channel
可以是无缓冲(同步)或有缓冲(异步)的。
示例 3:使用无缓冲的channel
无缓冲的channel
要求发送和接收必须同步,即发送和接收操作必须同时准备好,才能完成数据传递。
package main
import (
"fmt"
)
func sendData(ch chan int) {
ch <- 10 // 将数据发送到 channel
fmt.Println("Data sent to channel")
}
func main() {
ch := make(chan int) // 创建无缓冲 channel
go sendData(ch) // 启动一个 goroutine 发送数据
data := <-ch // 从 channel 接收数据
fmt.Println("Received data:", data)
}
在这个例子中:
ch := make(chan int)
创建了一个无缓冲的channel
。sendData
函数将数据发送到channel
,同时主函数从channel
接收数据。这两个操作是同步完成的。
示例 4:使用有缓冲的channel
有缓冲的channel
允许在缓冲区满之前发送多个数据,而不需要立即接收。
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 3) // 创建一个缓冲区大小为 3 的 channel
ch <- 1
ch <- 2
ch <- 3 // 可以发送三个数据到 channel 而无需立即接收
fmt.Println(<-ch) // 从 channel 接收数据:1
fmt.Println(<-ch) // 从 channel 接收数据:2
fmt.Println(<-ch) // 从 channel 接收数据:3
}
在这个例子中:
make(chan int, 3)
创建了一个缓冲区大小为3的channel
。- 可以连续发送三个数据到
channel
,在缓冲区未满时不需要立即接收。
3. 使用channel
同步goroutine
可以使用channel
来同步多个goroutine
的执行,例如在一个goroutine
完成任务后通知另一个goroutine
继续执行。
示例 5:使用channel
同步goroutine
package main
import (
"fmt"
)
func worker(done chan bool) {
fmt.Println("Working...")
done <- true // 完成任务后发送通知
}
func main() {
done := make(chan bool) // 创建一个无缓冲 channel
go worker(done) // 启动 worker goroutine
<-done // 等待 worker goroutine 完成
fmt.Println("Worker finished")
}
在这个例子中:
done
是一个无缓冲的channel
,用于同步main
函数和worker
函数。- 当
worker
完成任务后,会向done
发送一个值,main
函数会阻塞在<-done
直到接收到该值。
4. ✨ select
多路复用
select
用于同时监听多个channel
操作,当多个channel
都准备好时,select
会随机选择一个执行。
示例 6:select
的使用
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "Message from ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "Message from ch2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
}
在这个例子中:
select
语句会等待ch1
或ch2
中的一个准备好,接收到第一个消息后输出结果。- 因为
ch1
会先于ch2
发送消息,所以会先打印来自ch1
的消息。
5. close
关闭channel
关闭channel
可以告知接收方不再有数据发送。close
关闭channel
后不能再次向其中发送数据,否则会引发运行时恐慌。
示例 7:关闭channel
package main
import (
"fmt"
)
func producer(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 关闭 channel
}
func main() {
ch := make(chan int)
go producer(ch)
for num := range ch { // 迭代接收数据,直到 channel 关闭
fmt.Println(num)
}
fmt.Println("Channel closed")
}
在这个例子中:
- ✨
close(ch)
用于关闭channel
,通知接收方没有更多数据。 - ✨
for num := range ch
会自动接收channel
中的数据,直到channel
关闭。
6. goroutine
、channel
和并发编程的最佳实践
- 避免在多个
goroutine
中同时写入同一个channel
:多个goroutine
写入同一个channel
时,需要确保并发安全,通常需要借助sync.Mutex
或sync.WaitGroup
来控制。 - 不要对已关闭的
channel
写入数据:向已关闭的channel
写入数据会导致运行时恐慌,因此确保只关闭一次channel
,并且关闭后不再写入。 - 利用
select
处理多通道场景:select
可以实现多路复用,有效处理多个channel
的同时监听需求。 - 考虑使用带缓冲的
channel
来提升性能:对于一些高吞吐量场景,有缓冲的channel
可以减少阻塞,提高效率。
总结
goroutine
:是Go语言中并发执行的轻量级线程,通过go
关键字启动。channel
:是goroutine
之间进行通信和同步的工具,分为无缓冲和有缓冲两种。select
:用于监听多个channel
操作,方便处理多路复用。close
:关闭channel
后可以通知接收方不再有数据发送。