上一篇介绍了atomic
包以及互斥锁 mutex
来解决并发竞争状态的问题。这一篇主要来介绍go
中与goroutine
经常搭档的好兄弟channel
channel
不仅可以可以来用消除竞争状态,还可以用于不同的goroutine
中进行通信,发送与接受数据。chaanel的定义有两种,分为有缓存与无缓冲
chan1 := make(chan int) // 创建一个无缓冲的 整形 channel
chan2 := make(chan int,2)// 创建一个有缓冲的 整形 channel
上面的代码片段,我们分别创建了一个无缓冲的channel
与一个有缓冲的channel
。channel
的创建是使用make(chan type,[lenght])
来创建,如果指定了第二个参数length
表示创建了一个长度为length
的有缓存channel
,反之我们称之为无缓冲。
var number int
func main() {
chan1 := make(chan int) //创建一个无缓冲的 整形channel
go numberAdd(chan1)
fmt.Printf("改变之后的number:%d\r\n",<-chan1)
//改变之后的number:1
}
func numberAdd(c chan int) {
number ;
c<-number; //往chan写值
}
这里我们创建了一个整形的channel
在goroutine
中往chan
中写值,在main函数中取值。
goroutine
同时准备好才能完成发送和接收操作。如果两个 goroutine
没有同时准备好,通道会导致先执行发送或接收操作的 goroutine
阻塞等待。 groutine
同时准备好。只有在通道中没有空间容纳新值的时候,发送动作才会发送阻塞;只有在通道中没有值要接收时,接收动作才会阻塞。 下面来看两个例子
var wait sync.WaitGroup
const needProcessNumber = 3 //需要三次加工
func main() {
wait.Add(1)
sausage := make(chan int) // 腊肠
go processing(sausage) //开始加工程序
sausage<-1 //开始第一次加工
wait.Wait()
}
func processing(sausage chan int) {
defer wait.Done()
for {
nowNumber := <-sausage
fmt.Printf("第%d次加工开始\r\n",nowNumber)
for i:=1; i<=10; i {
fmt.Printf("%d \r\n",i*10)
}
fmt.Printf("第%d次加工结束\r\n",nowNumber)
if nowNumber==needProcessNumber{
fmt.Printf("新鲜的腊肠出炉了\r\n")
close(sausage)
return
}else {
go processing(sausage) //等待下一次加工开始
}
nowNumber
sausage <- nowNumber
//这里会加锁直到流程交接结束
}
}
这个例子创建了一个Int
无缓冲通道来表示腊肠,做一个腊肠需要三次加工,main
函数中创建了一个wait
来等待加工完成。准备一个加工的goroutine processing
等待第一个杯子准备就绪的信号,当接收到第一个信号时,开始加工,然后等待当前加工完成,如果当前goroutine
不是第三次加工的goroutine
,那么准备下一个加工程序开始,进入下一个goroutine
,直到第三次加工完成。
var wait sync.WaitGroup
const (
maxTask = 10 //最大处理工作数
workerNumber = 3 //当前工人数
)
func main() {
wait.Add(workerNumber) //等到所有的work都结束
tasks := make(chan int,maxTask)
for workerOnline:=1;workerOnline<=workerNumber;workerOnline {
go worker(tasks,workerOnline)
}
//增加十个需要处理的工作
for i:=1;i<=maxTask ; i {
tasks<-i
}
close(tasks)//所有工作完成
wait.Wait()
}
//员工开始工作
func worker(task chan int,workNumber int) {
defer wait.Done()
for{
taskNumber,ok := <-task
if !ok {
fmt.Printf("工人:%d 没有工作可以做了\r\n",workNumber)
return
}
fmt.Printf("工人:%d 开始工作,当前任务编号:%d\r\n",workNumber,taskNumber)
workTime := rand.Int63n(100)
time.Sleep(time.Duration(workTime)*time.Millisecond)
fmt.Printf("工人:%d 工作完成,当前任务编号:%d\r\n",workNumber,taskNumber)
}
}
这里我们声明了一个容量为10
的有缓冲通道task
来表示总共有十个任务需要3
个员工来处理。每个员工是一个goroutine
来单独完成工作。员工首先准备就绪,然后等待任务的下发。当监听到有任务进入时,开始完成工作,直到监听到task
通道已经关闭。需要注意的是我们在新增完10个任务时就已经关闭了channel
,这个时候goroutine
仍然可以从channel
取值,直到取到的返回数值是零值
,如果你这个时候获取了channel
的标志位,那么会返回一个false
,所以我们判断channel
是否关闭应该用这个标志位来判断。