在go语言中channel是一个内置的数据类型,用于多个goroutine间通信,两端分为发送端和接收端。根据是否有缓存可以分为有缓冲channel和无缓冲channel两种类型。
1. 初始化
func main() {
// 有缓存channel
bufCh := make(chan struct{}, 10)
// 无缓冲channel
unBufCh := make(chan struct{})
}
2. channel的元素类型
在go中channel的元素可以是任意类型,比如:内置的类型(int, string, float64等)、指针类型(*int等)、自定义结构体、空结构、数组等,可以根据具体的场景灵活使用。
2.1 空struct的场景
空struct{}在go语言中是不占用内存空间的,作为一个占位来使用,通常有两个使用场景:
- 使用map对元素进行去重,key为元素,value为struct{},可以减少内存空间的使用;
- 用于channel通信,当channel只是用于通知操作而不是传递真实数据是会使用struct{},比如:阻塞操作,该场景下通常都是无缓冲channel
func main() {
ch := make(chan struct{})
go func() {
<-ch
fmt.Println("Done")
}()
ch <- struct{}{}
close(ch)
}
2.2 自定义结构体
type A struct {
index int
}
func main() {
ch := make(chan A, 5)
go func() {
for {
select {
case a, ok := <-ch:
if !ok {
fmt.Println("Done")
return
}
fmt.Println("a:", a.index)
default:
time.Sleep(10 * time.Millisecond)
}
}
}()
for i := 0; i < 20; i++ {
ch <- A{i}
}
close(ch)
time.Sleep(time.Second)
}
3. 遍历channel
package main
import (
"fmt"
)
type A struct {
index int
}
func main() {
ch := make(chan A, 10)
for i := 0; i < 10; i++ {
ch <- A{i}
}
close(ch)
for v := range ch {
fmt.Println(v.index)
}
}
4. channel限制
根据发送端和接收端在场景上可以把channel划分为只读和只写两种属性,只读只能在接收端读取数据,而不能发送数据,只写只能在发送端写数据,而不能读数据。通常只读和只写的限制用在函数参数中对函数体内的操作进行限制,只能进行有限制的操作。
4.1 只读限制
下边的例子中在recv接收一个只读属性的channel,在函数体内接收数据并打印,如果向ch中发送数据,编译器会报错:Invalid operation: ch <- A{} (send to the receive-only type <-chan A)
package main
import "fmt"
type A struct {
index int
}
func main() {
ch := make(chan A)
go recv(ch)
ch <- A{1}
}
func recv(ch <-chan A) {
data := <-ch
fmt.Println("recv", data.index)
}
4.2 只写属性
下边的例子是只写属性,如果读channel,编译器会报错:Invalid operation: <-ch (receive from the send-only type chan<- A)
package main
import "fmt"
type A struct {
index int
}
func main() {
ch := make(chan A)
go recv(ch)
send(ch)
}
func recv(ch <-chan A) {
data := <-ch
fmt.Println("recv", data.index)
}
func send(ch chan<- A) {
ch <- A{2}
}
5. 如何判断数据是否已经发送结束?
channel读取数据的时候接收两个参数,第一个是接收到的数据,第二个是标识channel是否已经关闭,在开发过程中通常在select case分支中接收这两个参数,判断第二个标识是否为true,如果是false则表明数据发送完成,channel已经正常关闭,可以处理后续逻辑。
package main
import (
"fmt"
"time"
)
type A struct {
index int
}
func main() {
ch := make(chan A, 10)
go func() {
for {
select {
case a, ok := <-ch:
if !ok {
fmt.Println("channel closed")
return
}
fmt.Printf("%d\n", a.index)
default:
time.Sleep(time.Millisecond * 10)
}
}
}()
for i := 0; i < 10; i++ {
ch <- A{i}
}
close(ch)
time.Sleep(time.Second)
}
6. 源码分析
6.1 为什么向已关闭的channel发送数据会Panic?
在channel源码的结构定义中有一个closed的字段标识channel是否已经关闭,发送数据之前会校验channel是否可达,并校验是否已经关闭,如果关闭,即进行panic报错。
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
if c == nil {
if !block {
return false
}
gopark(nil, nil, waitReasonChanSendNilChan, traceBlockForever, 2)
throw("unreachable")
}
if debugChan {
print("chansend: chan=", c, "\n")
}
if raceenabled {
racereadpc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(chansend))
}
lock(&c.lock)
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
}
6.2 为什么关闭已经关闭的channel会Panic?
closechan在关闭之前会校验channel是否为nil,如果为nil会panic,然后会校验channel是否已经关闭,关闭会panic,没有关闭会值性关闭程序。
func closechan(c *hchan) {
// chanel不存在
if c == nil {
panic(plainError("close of nil channel"))
}
lock(&c.lock)
if c.closed != 0 {
// 已经关闭了
unlock(&c.lock)
panic(plainError("close of closed channel"))
}
if raceenabled {
callerpc := getcallerpc()
racewritepc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(closechan))
racerelease(c.raceaddr())
}
}
6.3 channel是否是并发安全的?
channel本身是并发安全的,因为在hchan的结构提中使用了互斥锁,对其它字段进行并发保护。虽然本身是并发安全的,但是在实际使用中还是需要注意panic的问题及并发读取数据竞争的问题。
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。