积木成楼
首页 / golang

go design (五) channel

2022-01-10 · golang · 约 22 分钟

go channel 的设计与实现

golang 中推崇的金句就是 不要通过共享内存来通信,要通过通信的方式来共享内存,其通信的载体就是 channel , golang 特有的关键字(数据结构),在 golang 中要实现并发编程成本很低, 一个 go 关键词 就可以启动一个 goroutine ,那么多个 goroutine 之间的数据传输该怎么处理呢?就有了 channel 通道,这种数据类型 来帮助在 多个 goroutine 进行信息传输。

从实例开始 channel 介绍之旅

创建与使用

缓存与不带缓存的 channel

c := make(chan int) // 不带缓存的 channel
c := make(chan int, 0) // 不带缓存的 channel
c := make(chan int, 2) // 带缓存的 channel
var s chan int // 特殊 channel nil

fmt.Println(c, s) // 0xc000086060 <nil>
//堵塞 
func main() {
	c := make(chan int) // 不带缓存的 channel

	go func() {
		time.Sleep(1 * time.Second)
		ok := <-c
		fmt.Println(ok)
	}()

	c <- 1
	time.Sleep(1 * time.Second)
}
//带缓冲 
func main() {
	c := make(chan int,2) // 带缓存的 channel

	go func() {
		for{
			time.Sleep(1 * time.Second)
			ok := <-c
			fmt.Println(ok)
		}
	}()

	c <- 1
	c <- 2
	time.Sleep(3 * time.Second)
}

channel 的两个属性

c := make(chan int, 2) // 带缓存的 channel
fmt.Println(len(c), cap(c))// 0  2 

select 为多 channel 处理而生

在上述的使用中 如果在 一个协程中 使用多个 channel,如果一个 channel堵塞 ,那么代码就没法 执行到 下一个channel` ,从而导致运行时 死锁

比如:

c := make(chan int) // 不带缓存的 channel
d := make(chan int) // 不带缓存的 channel
go func() {
    for {
        ok := <-c
        ok2 := <-d
        fmt.Println(ok,ok2)
    }
}()
d <- 1
c <- 1
time.Sleep(3 * time.Second)
//fatal error: all goroutines are asleep - deadlock!

这种情况下如果不知道 c,d 谁会先发送数据的情况 就会 直接报错,相互死锁,程序中断。

for rangechannel 的配合使用

```go
func main() {
    c := make(chan int, 2) // 带缓存的 channel

    go func() {
        for val := range c {
            fmt.Println(val)
        }
    }()

    for i := 0; i < 10; i++ {
        c <- i
        if i == 5 {
            close(c)
            break
        }
    }

    time.Sleep(time.Second * 1)
}
```

close() 函数与 channel 的关闭

用法-超时控制

用法-控制并发执行协程数量(协程池)

用法-生产消费模型

golang 中的 设计与实现

到目前为止,我们把 channel 的基础功能 过了一遍, 感觉上确实是挺复杂与强大的,接下来会去看看 channel 的数据结构,跟写入,读取的流程,通过这些流程,更好的理解为什么,channel 会有这样的特性.

channel 实现的数据结构

// runtime/chan.go
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
}

type waitq struct {
	first *sudog
	last  *sudog
}

type sudog struct {
	// The following fields are protected by the hchan.lock of the
	// channel this sudog is blocking on. shrinkstack depends on
	// this for sudogs involved in channel ops.

	g *g

	next *sudog
	prev *sudog
	elem unsafe.Pointer // data element (may point to stack)

	// The following fields are never accessed concurrently.
	// For channels, waitlink is only accessed by g.
	// For semaphores, all fields (including the ones above)
	// are only accessed when holding a semaRoot lock.

	acquiretime int64
	releasetime int64
	ticket      uint32

	// isSelect indicates g is participating in a select, so
	// g.selectDone must be CAS'd to win the wake-up race.
	isSelect bool

	// success indicates whether communication over channel c
	// succeeded. It is true if the goroutine was awoken because a
	// value was delivered over channel c, and false if awoken
	// because c was closed.
	success bool

	parent   *sudog // semaRoot binary tree
	waitlink *sudog // g.waiting list or semaRoot
	waittail *sudog // semaRoot
	c        *hchan // channel
}

channel 的发送

channel 的接受

close

← 返回文章列表