go语言通道插入0_一文读透GO语言的通道

channel是GO语言并发体系中的主推的通信机制,它可以让一个 goroutine 通过它给另一个 goroutine 发送值信息。每个 channel 都有一个特殊的类型,也就是 channels 可发送数据的类型。一个可以发送 int 类型数据的 channel 一般写为 chan int。Go语言提倡使用通信的方法代替共享内存,当一个资源需要在 goroutine 之间共享时,通道在 goroutine 之间架起了一个管道,并提供了确保同步交换数据的机制。声明通道时,需要指定将要被共享的数据的类型。可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或者指针。

channel是一种特殊的类型是保证协程安全的,也就是在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。而且遵循先入先出(First In First Out)的规则,保证收发数据的顺序。这两个特性是channel可以产生共享内存功能的重要原因。。读完这一讲,下面我们就可以继续我们的例子,开始GO语言并发的实战了

一、通道的声明

1.经典方式声明

通过使用chan类型,其声明方式如下:

var name chan type

其中type表示通道内的数据类型;name:通道的变量名称,不过这样创建的通道只是空值 nil,一般来说都是通道都是通过make函数创建的。

2.make方式

make函数可以创建通道格式如下:

name := make(chan type)

3.创建带有缓冲的通道

后面会讲到缓冲通道的概念,这里先说他的定义方式

name := make(chan type, size)

其中type表示通道内的数据类型;name:通道的变量名称,size代表缓冲的长度。

二、通道的数据收发

1. 通道的数据发送

通道的发送的操作符

chan

注意,如果将数据发送至一个无缓冲的通道中,如果数据一直都没有接收,那么发送操作将持续阻塞。但是GO的编译器能够发现明显的错误,比如

ch := make(chan int) // 创建一个整型通道

ch

编译时会报错:fatal error: all goroutines are asleep - deadlock!

2.通道的数据接收

通道接收数据的操作符也是

1) 阻塞接收数据

阻塞模式接收数据时,将接收变量作为

data :=

执行该语句时将会阻塞,直到接收到数据并赋值给 data 变量。

如需要忽略接收的数据,则将data变量省略,具体格式如下:

2) 非阻塞接收数据

使用非阻塞方式从通道接收数据时,语句不会发生阻塞,格式如下:

data, ok :=

非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。一般只配合select语句配合定时器做超时检测时使用。

三、channel的超时检测

Go语言没有提供直接的超时处理机制,一般使用select关键字来设置超时。Select虽然不是专为超时而设计的,却能很方便的解决超时问题,因为select的特点是只要其中有一个 case 已经完成,程序就会继续往下执行,而不会考虑其他 case 的情况。select 的用法与 switch 语言非常类似,由 select 开始一个新的选择块,每个选择条件由 case 语句来描述。

但是与switch 语句相比,select 有比较多的限制,其中最大的一条限制就是每个 case 语句里必须是一个 IO 操作,结构如下:

select {

case

// 如果chan1成功读到数据,则进行该case处理语句

case chan2

// 如果成功向chan2写入数据,则进行该case处理语句

default:

// 如果上面都没有成功,则进入default处理流程

}

比如在示例代码中,我们创建了一个用于传数据的channel,和一个用于超时退出的通道quit,并使用select来接收channel的数据,其中如果程序运行到3s时,退出通道会被置为true,这时

package main

import (

"fmt"

"time"

)

func main() {

ch := make(chan int) //设置一个传送数据的channel

quit := make(chan bool) //设置一个超时传送的channel

//新开一个协程

go func() {

for {

select {

case num :=

fmt.Println("num = ", num)

case

fmt.Println("超时")

quit

}

}

}() //别忘了()

for i := 0; i < 5; i++ {

ch

time.Sleep(time.Second)

}

fmt.Println("程序结束")

/*运行结果为

num = 0

num = 1

num = 2

num = 3

num = 4

超时

程序结束

*/

}

四、通道数据收发的注意事项

1.通道的收发操作在不同的两个 goroutine 间进行。由于通道的数据在没有接收方处理时,数据发送方会持续阻塞,因此通道的接收必定在另外一个 goroutine 中进行。

2.接收将持续阻塞直到发送方发送数据。如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。

3.每次只接收一个元素。

第4节 深入理解GO语言中的channel

GO语言中的有关chan的代码位置在GOPATH\src\runtime\chan.go,阅读代码可以发现channel 内部就是一个带锁的队列。

1.基本数据结构

type hchan struct {

qcount uint // 队列中数据个数

dataqsiz uint // channel 大小

buf unsafe.Pointer // 存放数据的环形数组

elemsize uint16 // channel 中数据类型的大小

closed uint32 // 表示 channel 是否关闭

elemtype *_type // 元素数据类型

sendx uint // send 的数组索引

recvx uint // recv 的数组索引

recvq waitq // 由 recv 行为(也就是

sendq waitq // 由 send 行为 (也就是 ch

// 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

}

如果是一个无缓冲的chan只需要用一个 lock来确保无竞争冲突。而带缓冲的chan其实就是一个环形队列,通过sendx 和 recvx 分别用来记录发送、接收的位置。

2.数据发送的实现

其实数据发送和接收的逻辑比类似,这里我们只举数据发送的例子。

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {

if c == nil {//正确性检查

if !block {

return false

}

gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)

throw("unreachable")

}

if debugChan {

print("chansend: chan=", c, "\n")

}

if raceenabled {

racereadpc(c.raceaddr(), callerpc, funcPC(chansend))//重置竞争标志位

}

// Fast path: check for failed non-blocking operation without acquiring the lock.

//

// After observing that the channel is not closed, we observe that the channel is

// not ready for sending. Each of these observations is a single word-sized read

// (first c.closed and second c.recvq.first or c.qcount depending on kind of channel).

// Because a closed channel cannot transition from 'ready for sending' to

// 'not ready for sending', even if the channel is closed between the two observations,

// they imply a moment between the two when the channel was both not yet closed

// and not ready for sending. We behave as if we observed the channel at that moment,

// and report that the send cannot proceed.

//

// It is okay if the reads are reordered here: if we observe that the channel is not

// ready for sending and then observe that it is not closed, that implies that the

// channel wasn't closed during the first observation.

if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||

(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {

return false//如果队列被关闭等情况,返回错误

}

var t0 int64

if blockprofilerate > 0 {

t0 = cputicks()

}

lock(&c.lock)

if c.closed != 0 {

unlock(&c.lock)

panic(plainError("send on closed channel"))

}

if sg := c.recvq.dequeue(); sg != nil {

// Found a waiting receiver. We pass the value we want to send

// directly to the receiver, bypassing the channel buffer (if any).

send(c, sg, ep, func() { unlock(&c.lock) }, 3)

return true

}

if c.qcount < c.dataqsiz {

// Space is available in the channel buffer. Enqueue the element to send.

qp := chanbuf(c, c.sendx)

if raceenabled {

raceacquire(qp)

racerelease(qp)

}

typedmemmove(c.elemtype, qp, ep)

c.sendx++

if c.sendx == c.dataqsiz {

c.sendx = 0

}

c.qcount++

unlock(&c.lock)

return true

}

if !block {

unlock(&c.lock)

return false

}

// Block on the channel. Some receiver will complete our operation for us.

gp := getg()

mysg := acquireSudog()

mysg.releasetime = 0

if t0 != 0 {

mysg.releasetime = -1

}

// No stack splits between assigning elem and enqueuing mysg

// on gp.waiting where copystack can find it.

mysg.elem = ep

mysg.waitlink = nil

mysg.g = gp

mysg.isSelect = false

mysg.c = c

gp.waiting = mysg

gp.param = nil

c.sendq.enqueue(mysg)

goparkunlock(&c.lock, waitReasonChanSend, traceEvGoBlockSend, 3)

// Ensure the value being sent is kept alive until the

// receiver copies it out. The sudog has a pointer to the

// stack object, but sudogs aren't considered as roots of the

// stack tracer.

KeepAlive(ep)

// someone woke us up.

if mysg != gp.waiting {

throw("G waiting list is corrupted")

}

gp.waiting = nil

if gp.param == nil {

if c.closed == 0 {

throw("chansend: spurious wakeup")

}

panic(plainError("send on closed channel"))

}

gp.param = nil

if mysg.releasetime > 0 {

blockevent(mysg.releasetime-t0, 2)

}

mysg.c = nil

releaseSudog(mysg)

return true

}

我们看到在发送数据时,首先获取lock,然后进行竞争检查、指针检查等,然后更新读写位置,记录数据,并释放锁,如果,当前hchan.buf 无可用空间,则将操作阻塞。

所以从本质上讲channel就是一个基于锁的循环队列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值