一、channel
1.1 并发通信模型
并发通信模型有两种常见的:共享数据和消息。共享数据是指多个并发单元分别保持对同一个数据的引用,最常见的就是共享内存。 当多个goroutine访问共享数据时,就涉及到锁了。
互斥锁:
var (
counter int
wg sync.WaitGroup
mutex sync.Mutex
)
func main() {
wg.Add(2)
go incCounter(1)
go incCounter(2)
wg.Wait()
fmt.Println(counter)
}
func incCounter(id int) {
defer wg.Done()
mutex.Lock()
{
value := counter
runtime.Gosched()
value++
counter = value
}
mutex.Unlock()
}
WaitGroup一共有三个方法。add(int)设置子任务的数量,Done方法将waitGroup计数减一,表示完成一个子任务。Wait方法用于阻塞调用者,直到 WaitGroup 的计数值为0,即所有子任务都完成。
Go语言提供了消息机制作为通信方式。不同线程间通过消息来通信。go提供的消息通信机制成为channel。channel是进程内的通信方式,跨进程通信推荐分布式系统,如http通信协议。
channel 是类型相关的,也就是说,一个 channel 只能传递一种类型的值,这个类型需要在声明 channel 时指定。
1.2 创建通道
ch1 := make(chan int) // 创建一个整型类型的通道
ch2 := make(chan interface{}) // 创建一个空接口类型的通道, 可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip) // 创建Equip指针类型的通道, 可以存放*Equip
1.3 发送数据
通道变量 <- 值
- 示例
// 创建一个空接口通道
ch := make(chan interface{})
// 将0放入通道中
ch <- 0
// 将hello字符串放入通道中
ch <- "hello"
注意在发数据的时候,如果一直没有接收方接收消息,那发送操作将持续阻塞。所有的goroutine(包括main)都处于等待状态
1.4 接收数据
首先,通道的收发数据在两个不同的goroutine之间进行。通道的数据在没有接收方处理时,数据发送方会持续阻塞,因此通道的接收必定在另外一个 goroutine 中进行。
接收将持续阻塞直到发送方发送数据。如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。
-
阻塞接受数据
data := <- ch执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。
-
非阻塞接受数据
data,ok := <- chdata:表示接收到的数据。未接收到数据时,data 为通道类型的零值。
ok:表示是否接收到数据。
非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel 进行
-
接受并忽略数据
<- ch执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。
可以通过通道在 goroutine 间阻塞收发实现并发同步。
package main
import (
"fmt"
)
func main() {
// 构建一个通道
ch := make(chan int)
// 开启一个并发匿名函数
go func() {
fmt.Println("start goroutine")
// 通过通道通知main的goroutine
ch <- 0
fmt.Println("exit goroutine")
}()
fmt.Println("wait goroutine")
// 等待匿名goroutine
<-ch
fmt.Println("all done")
}
- 循环接收
for data := range ch {
}
示例:
package main
import (
"fmt"
"time"
)
func main() {
// 构建一个通道
ch := make(chan int)
// 开启一个并发匿名函数
go func() {
// 从3循环到0
for i := 3; i >= 0; i-- {
// 发送3到0之间的数值
ch <- i
// 每次发送完时等待
time.Sleep(time.Second)
}
}()
// 遍历接收通道数据
for data := range ch {
// 打印通道数据
fmt.Println(data)
// 当遇到数据0时, 退出接收循环
if data == 0 {
break
}
}
}