通道的定义
通道是Go用于协程之间进行通讯的工具。Go的通道(channel)是一种特殊的类型,可以让你发送类型化的数据,在两个Go协程(goroutine)之间进行同步、数据传递。它是并发安全的,也就是说在多个协程之间传递对共享数据的所有权,避免协程之间的数据竞争。因此,通常将通道用于在两个协程之间传递数据。
在Go语言中,通道可以传递任何类型的值。你可以将它们视为管道或队列,通过它们可以发送或接收值。这些值都有一个特定的类型,例如int或string,你也可以通过通道发送或接收自定义的数据类型,比如结构体、数组、切片、映射等,甚至是其他通道。
但需要注意的是,通道在声明时需要为其指定元素类型,一个通道只能传输一种类型的数据,不同类型的数据需要通过不同的通道来传输。例如,使用chan int
声明的通道只能传输整型数据,使用chan string
声明的通道只能传输字符串。
示例
单向通道:
package mAIn
import "fmt"
func message(passMsg chan<- string){
passMsg <- "A message from message function"
}
func main(){
defaultMessage := make(chan string)
go message(defaultMessage)
fmt.Println(<-defaultMessage)
}
在此示例中,我们创建一个发送给主功能的消息函数。我们通过 "chan<-" 关键字来确保这个频道仅用于发送消息。
2、双向通道
package main
import "fmt"
func message(passMsg chan string){
passMsg <- "A message from message function"
msg := <- passMsg
fmt.Println(msg)
}
func main(){
defaultMessage := make(chan string)
go message(defaultMessage)
defaultMessage <- "A message from main function"
}
在此示例中,我们创建一个从主函数发送并接收到消息函数的消息。这是一个双向通道,我们通过 "chan" 关键字来建立。在主函数,我们给通道发送消息,在消息函数中,我们收到主函数的消息并在该函数中发送一个新消息。
3、缓冲的通道:
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
在此示例中,我们创建了一个带缓冲的通道,该通道可以存储两个整数值。我们向通道发送两个值,然后接收这两个值,不会阻塞,因为通道已经有缓冲区存储这两个值。
4、关闭通道和检测通道是否关闭
ch := make(chan int)
close(ch)
//你可以通过让接收表达式返回一个二参数来检查通道是否被关闭。如果ok为false,那说明通道已经没有数据可以接收,并且已经被关闭。例如:
v, ok := <-ch
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 2)
// 发送方
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch) // 不再需要发送数据,关闭channel
}()
// 接收方
for {
if v, ok := <-ch; ok {
fmt.Println(v)
} else {
fmt.Println("Channel closed!")
break
}
}
}
在这个示例中,通过检查 ok 的值来判断通道是否已经被关闭。一旦检测到通道已经被关闭,就 break 循环,终止读取。
5、使用通道来进行锁的竞争,避免并发
package main
import (
"fmt"
"sync"
)
var counter = 0
func increment(wg *sync.WaitGroup, ch chan bool) {
ch <- true
counter++
<-ch
wg.Done()
}
func main() {
var wg sync.WaitGroup
ch := make(chan bool, 1)
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg, ch)
}
wg.Wait()
fmt.Println("Final Counter:", counter)
}
在这个示例中,我们有一个名为"counter"的全局变量,我们希望在多个goroutine中并发地递增这个变量。为了避免数据竞争,我们使用了一个缓冲区为1的通道。
在"increment"函数中,我们先发送一个值到通道,然后对"counter"进行递增。当递增完毕后,我们从通道接收值,这会释放通道缓冲区的空间,以便其他的goroutine可以发送值到通道。
因此,由于通道的缓冲区大小为1,只有一个goroutine能够对"counter"进行递增。这就实现了对"counter"访问的互斥,有效避免了数据竞争。
6、go-range读取通道的值
package main
import (
"fmt"
"time"
)
func produce(c chan int) {
for i := 0; i < 10; i++ {
c <- i
fmt.Println("send:", i)
time.Sleep(time.Millisecond * 500)
}
close(c)
}
func main() {
c := make(chan int, 5)
go produce(c)
// 使用 'range'循环读取通道数据
for v := range c {
fmt.Println("receive:", v)
}
}
在此示例中,produce
函数将数字0到9发送到通道,然后关闭通道。main函数中,使用for v := range c
循环读取通道数据,直到通道被关闭。每次从通道中读取数据并打印,只有当通道中没有数据并且通道被关闭时,才会退出循环。这是通过内建的close
函数把关闭信息传递给接收方实现的。