代码演示:
func main() {
// channel 声明
c := make(chan int)
c <- 1 // 向管道发送 1
<-c // 丢弃
s := <-c // 赋给s
// 或者声明string
//c := make(chan string)
//c <- "1"
}
说明:
make(chan int) // 表示无缓冲chanmake(chan bool, 0) // 无缓冲make(chan string, 2) // 有缓冲, 有数字就代表有缓冲ch <- x // 发送数据x = <- ch // 接收数据,赋给x<- ch // 接收数据,并丢弃func main() {
c := make(chan string)
c <- "ping"
fmt.Println(<-c)
}
func main() {
c := make(chan string)
// 还必须在前面开启这个协程
go func() {
fmt.Println(<-c)
}()
c <- "ping"
}
在go官方有这样一个建议:
不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存
怎么理解呢, 我分别给出两个例子就可以了解了
func watch(p *int) {
for true {
if *p == 1 {
fmt.Println("break....")
break
}
}
}
func main() {
i := 0
go watch(&i)
time.Sleep(time.Second * 2)
i = 1
time.Sleep(time.Second)
}
共享内存方案: i的地址给另一个协程, 典型的共享内存需要一直遍历, 消耗系统资源
func watch(c chan int) {
// 管道不需要一遍一遍去查询
if <-c == 1 {
fmt.Println("hello....")
}
}
func main() {
c := make(chan int)
go watch(c)
time.Sleep(time.Second * 2)
c <- 1
time.Sleep(time.Second)
}
通信方式: 不需要一直遍历, 提高系统使用资源
总共需要三个成员
需要一个缓存buffer

发送数据满之后,发送会阻塞,这时会有一个等待队列


合并起来,chan 设计需要3个重要的成员

hchan 数据结构就是go官方实现的chan 底层设计

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
}
其中下面几个成员组成了一个缓存区环形缓存

qcount uint // total data in thequeue
dataqsiz uint // size of the circularqueue
buf unsafe.Pointer // points to an arrayof dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
下面两个成员组成了发送队列,其中是用的链表实现的

从这里看出是形成的链表

同理这两个成员组成了接收队列

另外还有一个互斥锁, 这个字段是锁所有字段的

下面是对hchan 中锁的认识 :
最后还有一个状态值字段, close 字段
1 : 表开启
0 :表关闭
channel 数据结构是go 中的一等公民
chan 成员一览表:

上面讲解了数据结构,这里就是算法了


code:
func watch(c chan int) {
// 管道不需要一遍一遍去查询
if <-c == 1 {
fmt.Println("hello....")
}
}
func main() {
c := make(chan int)
go watch(c)
time.Sleep(time.Second * 2)
c <- 1
time.Sleep(time.Second)
}
实现步骤:

实现步骤:

原理:

实现步骤:
3. 把自己包装成sudog
4. sudog 放入sendq 队列
5. 休眠并解锁
6. 被唤醒后,数据已经被取走,维护其他数据
编译阶段, 会把 <- 转化为 runtime.chansend1()


接收步骤:


原理
实现

原理
实现

原理
实现
3. 判断没有G在发送队列等待
4. 判断此channel 无缓存
5. 将自己包装成sudog
6. sudog 放入接收等待队列,休眠
7. 唤醒时,发送的G已经把数据拷贝到位
只用 <- 接收时, 永远是阻塞的,什么时候用非阻塞呢, 答案是 select

code:
func main() {
// 快捷键补全 ctrl + alt + v (qq音乐会冲突)
c1 := make(chan int, 5)
c2 := make(chan int)
select {
case <-c1:
fmt.Println("c1")
case c2 <- 1:
fmt.Println("c2")
default:
fmt.Println("none")
}
}
// 倒计时 定时器
t := time.NewTimer(time.Second)
// 倒计数1s 后定时器的channel 会塞入一个数据, 这时就可以取出一个数据了
<-t.C
fmt.Println("hello...")
