go基础知识-channel

286 阅读1分钟

什么是channel?

go语言采用的是CSP(通信顺序进程)并发模型,实现了其中的Process/Channel理论(对应go语言中的goroutine/channel),channel在go语言中作为多线程中的数据共享实现,通常与goroutine搭配进行多线程通信。

channel是否线程安全?

安全。channel设计用于多线程之间的通信,因为必须保证线程安全性。

打开方式:

//初始化方式
var ch1 = make(chan int)    //无缓冲
var ch2 = make(chan int, 1) //有1缓冲

//for range 方式读取ch1
go func() {
    for num := range ch1 {
        fmt.Println(num)
    }
    fmt.Println("ch1 close")
}()
//for select方式读取ch2
go func() {
Loop:
    for {
        select {
        case num, ok := <-ch2:
            if ok {
                fmt.Println(num)
            } else {
                fmt.Println("ch2 close")
                break Loop
            }
        }
    }
}()
ch1 <- 1
ch2 <- 2
time.Sleep(1 * time.Second)
close(ch1)
close(ch2)
time.Sleep(1 * time.Second)

/**
输出:
1
2
ch2 close
ch1 close
*/

channel原理:

channel的实现可以在 rutime/chan.go中找到代码实现。

type hchan struct {
    qcount   uint           // 队列数据个数
    dataqsiz uint           // 环形队列大小
    buf      unsafe.Pointer // 存放实际数据的指针 unsafe.Pointer设计上避免GC
    elemsize uint16 // 元素类型大小
    closed   uint32 // channel关闭标识
    elemtype *_type // 类型元数据
    sendx    uint   // 发送数据 index
    recvx    uint   // 接收数据 index
    recvq    waitq  // 接收数据链表
    sendq    waitq  // 发送数据链表

    // 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 shrinki
    lock mutex //锁
}

type waitq struct {
    first *sudog
    last  *sudog
}

channel主要基于hchan结构实现,可以发现,其本质是环形队列实现。

发送数据时,通过调用chansend方法,实现逻辑如下:

//加锁,保证原子实现
lock(&c.lock)
//判断channel关闭则无法操作,panc
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
}
//channel有缓冲,且未满则把发送数据放入缓冲区,sendx下标后移
if c.qcount < c.dataqsiz {
    // Space is available in the channel buffer. Enqueue the element to send.
    qp := chanbuf(c, c.sendx)
    if raceenabled {
        racenotify(c, c.sendx, nil)
    }
    typedmemmove(c.elemtype, qp, ep)
    c.sendx++
    if c.sendx == c.dataqsiz {
        c.sendx = 0
    }
    c.qcount++
    unlock(&c.lock)
    return true
}

接收数据时,通过调用chanrecv方法,实现逻辑如下:

//加锁
lock(&c.lock)

if c.closed != 0 {
    //channel处于关闭状态,缓冲区依然有数据,可以继续执行
    if c.qcount == 0 {
        if raceenabled {
            raceacquire(c.raceaddr())
        }
        unlock(&c.lock)
        if ep != nil {
            typedmemclr(c.elemtype, ep)
        }
        return true, false
    }
    // The channel has been closed, but the channel's buffer have data.
} else {
    //发送队列中还有等待协程,则继续处理
    // Just found waiting sender with not closed.
    if sg := c.sendq.dequeue(); sg != nil {
        // Found a waiting sender. If buffer is size 0, receive value
        // directly from sender. Otherwise, receive from head of queue
        // and add sender's value to the tail of the queue (both map to
        // the same buffer slot because the queue is full).
        recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
        return true, true
    }
}
//缓冲区还有数据则copy出来处理
if c.qcount > 0 {
    // Receive directly from queue
    qp := chanbuf(c, c.recvx)
    if raceenabled {
        racenotify(c, c.recvx, nil)
    }
    if ep != nil {
        typedmemmove(c.elemtype, ep, qp)
    }
    typedmemclr(c.elemtype, qp)
    c.recvx++
    if c.recvx == c.dataqsiz {
        c.recvx = 0
    }
    c.qcount--
    unlock(&c.lock)
    return true, true
}