在Go 语言里,我们不仅可以使用原子函数和互斥锁来保证对共享资源的安全访问以及消除竞争状态,还可以使用通道。通过发送和接收需要共享的资源,在 goroutine 之间做同步。我们通过通道可以共享内置类型、命名类型、结构类型和引用类型的值或者指针。
// 创建无缓冲的整型通道
unbuffered := make(chan int)
// 创建有缓冲的字符串通道
buffered := make(chan string, 10)
// 通过通道发送一个字符串
buffered <- "Gopher"
// 从通道接收一个字符串
value := <-buffered
1、无缓冲的通道
无缓冲的通道是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。如果两个 goroutine 没有同时准备好,通道会导致先执行发送或者接收的通道的 goroutine 阻塞等待。这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
如下面代码,就是利用无缓冲通道实现的网球比赛
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var wg sync.WaitGroup
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
count := make(chan int)
wg.Add(2)
go player("Nadefe", count)
go player("Bobbbb", count)
count <- 1
wg.Wait()
}
func player(name string, count chan int) {
defer wg.Done()
for{
ball, ok := <- count
if (!ok){
fmt.Printf("%s win the gamne\n", name)
return
}
n := rand.Intn(100)
if n % 13 == 0 {
fmt.Printf("%s miss the game \n", name)
close(count)
return
}
fmt.Printf("%s hit the ball: %d \n", name, ball)
ball++
count <- ball
}
}
2、有缓冲的通道
有缓冲的通道是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。只有在通道中没有要接收的值时,接收动作才会阻塞。只有通道中没有可用的缓冲区容纳被发送的数值时,发送动作才会阻塞。则有缓冲通道与无缓冲通道之间的一个很大的不同就是:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道则没有这种保证。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
const (
GOROUTINENUMS = 4
TASKLOAD = 10
)
var wg sync.WaitGroup
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
task := make(chan string, TASKLOAD)
wg.Add(GOROUTINENUMS)
for i := 0; i < GOROUTINENUMS; i++ {
go worker(task, i)
}
for post := 0; post < TASKLOAD; post++{
task <- fmt.Sprintf("task %d", post)
}
close(task)
wg.Wait()
}
func worker(task chan string, worker int) {
defer wg.Done()
for{
value, ok := <- task
if !ok{
fmt.Printf("worker %d finish the work\n", worker)
return
}
fmt.Printf("worker %d start %s \n",worker, value)
sleep := rand.Int63n(100)
time.Sleep(time.Duration(sleep))
fmt.Printf("worker %d finish %s \n",worker, value)
}
}
从上面的代码可知,当通道关闭后,goroutine 依旧可以从通道接收数据,但是不能再向通道里发送数据。同时要注意的是,从一个已经关闭且没有任何数据的通道里获取数据,总会立刻返回,并返回一个通道类型的零值,此时,当从通道里面获取数据时,应该获取其状态值,再使用其数据。