Detailed explanation of [golang] channel

Time:2020-11-10

Channel is a type pipe through which messages can be sent and received between goroutines. It is the communication mode between goroutines provided by golang at the language level.

As we all know, go relies on a concurrency model called CSP (communicating sequential processes), which is implemented by channel. The core philosophy of go concurrency is not to communicate through shared memory; instead, to share memory through communication.

Here’s a simple example to demonstrate how go can communicate through a channel.

package main
import (
    "fmt"
    "time"
)
func goRoutineA(a <-chan int) {
    val := <-a
    fmt.Println("goRoutineA received the data", val)
}
func goRoutineB(b chan int) {
    val := <-b
    fmt.Println("goRoutineB  received the data", val)
}
func main() {
    ch := make(chan int, 3)
    go goRoutineA(ch)
    go goRoutineB(ch)
    ch <- 3
    time.Sleep(time.Second * 1)
}

The results were as follows

goRoutineA received the data 3

The above is just a simple example. Only goroutinea is output, but goroutineb is not executed. This shows that channel can only be read and written by one goroutine.

Next, we analyze the program execution process through the source code. Before speaking, if we do not understand the go concurrency and scheduling related knowledge. Please read this article

https://github.com/guyan0319/…

When it comes to channel, we have to mention the channel structure hchan.

hchan

The source code is in Src / 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
}

explain:

qcountUint / / the number of elements remaining in the current queue
dataqsizUint / / ring queue length, i.e. the size of buffer, i.e. make (Chan, t, n), n
bufunsafe.Pointer //Ring queue pointer
elemsizeUint16 / / size of each element
closedUint32 / / indicates whether the current channel is closed. After the channel is created, the field is set to 0, that is, the channel is opened; by calling close to set it to 1, the channel is closed.
elemtype* * Type / / element type, used for assignment during data transfer;
sendxUint andrecvxUint is the status field of the ring buffer, which indicates the current index support array of the buffer from which it can send and receive data.
recvqWaitq / / goroutine queue waiting to read messages
sendqWaitq / / goroutine queue waiting to write messages
lockMutex / / mutex, which locks the channel for each read / write operation, because send and receive must be mutually exclusive.

hereSudog stands for goroutine.

There are two ways to create a channel, one is a buffered channel, and the other is a non buffered channel

//With buffer
ch := make(chan Task, 3)
//Without buffer
ch := make(chan int)

Let’s start with buffering

ch := make(chan int, 3)

Buffer channel structure after channel creation

hchan struct {
    qcount uint : 0 
    dataqsiz uint : 3 
    buf unsafe.Pointer : 0xc00007e0e0 
    elemsize uint16 : 8 
    closed uint32 : 0 
    elemtype *runtime._type : &{
        size:8 
        ptrdata:0 
        hash:4149441018 
        tflag:7 
        align:8 
        fieldalign:8 
        kind:130 
        alg:0x55cdf0 
        gcdata:0x4d61b4 
        str:1055 
        ptrToThis:45152
        }
    sendx uint : 0 
    recvx uint : 0 
    recvq runtime.waitq : 
        {first:<nil> last:<nil>}
    sendq runtime.waitq : 
        {first:<nil> last:<nil>}
    lock runtime.mutex : 
        {key:0}
}

source code

func makechan(t *chantype, size int) *hchan {

   elem := t.elem
   ...
}

If we create a channel with buffer, the underlying data model is as follows:

Write data to channel

ch <- 3

The underlying hchan data flow is shown in the figure


Send operation summary

1. Lock the entire channel structure.

2. OK to write. attemptrecvqWait for goroutine from the wait queue, and then write the element directly to goroutine.

3. If recvq is empty, determines whether the buffer is available. If available, copies data from the current goroutine to the buffer.

4. If the buffer is full,ThenThe written element will be saved in the structure of the currently executing goroutine, and the current goroutine will be in theIn sendqQueue and suspend from runtime.

5. Write complete releases lock.

Here we should pay attention to the changes of several attributes buf, sendx and lock.

flow chart

Read operation from channel

Almost the same as a write operation

code

func goRoutineA(a <-chan int) {
   val := <-a
   fmt.Println("goRoutineA received the data", val)
}

The underlying hchan data flow is shown in the figure

Here we should pay attention to the changes of several attributes buf, sendx, recvx and lock.

Read operation summary

1. Get the channel global lock first

2. Try sendq to get the waiting goroutine from the waiting queue,

3. If there is a waiting goroutine and there is no buffer, take out the goroutine and read the data, then wake up the goroutine, finish reading and release the lock.

4. If there is a waiting goroutine and there is a buffer (at this time the buffer is full), take the data from the head of the buffer queue, and then take a goroutine from sendq. The data in the goroutine is saved to the end of the buf queue, and the lock is released after reading.

5. If there is no waiting goroutine and there is data in the buffer, read the buffer data directly and end the read to release the lock.

6. If there is no waiting goroutine and there is no buffer or the buffer is empty, the current goroutine will be addedrecvqLine up, go to sleep, and wait to be woken up by writing goroutine. End read release lock.

flow chart

Recvq and sendq structures

Recvq and sendq are basically linked lists, which look like the following

select

Select is used to listen for IO operations related to channel, and trigger corresponding actions when IO operations occur.

A simple example is as follows

package main

import (
   "fmt"
   "time"
)

func goRoutineD(ch chan int, i int) {
   time.Sleep(time.Second * 3)
   ch <- i
}
func goRoutineE(chs chan string, i string) {
   time.Sleep(time.Second * 3)
   chs <- i

}

func main() {
   ch := make(chan int, 5)
   chs := make(chan string, 5)

   go goRoutineD(ch, 5)
   go goRoutineE(chs, "ok")

    select {
    case msg := <-ch:
        fmt.Println(" received the data ", msg)
    case msgs := <-chs:
        fmt.Println(" received the data ", msgs)
    default:
        fmt.Println("no data received ")
        time.Sleep(time.Second * 1)
    }


}

Run the program. Because the current time is less than 3S, select default

no data received

To modify the program, we comment out the default and execute it several times. The result is

received the data 5

received the data ok

received the data ok

received the data ok

The select statement will block until an IO operation that can be executed is monitored. Here, the sleep time of goroutined and goroutinee is the same, which is 3S. From the output, we can see that the order of reading data from the channel is random.

Then modify the code to change the goroutined sleep time to 4S

func goRoutineD(ch chan int, i int) {
   time.Sleep(time.Second * 4)
   ch <- i
}

At this time, go routinee is executed first, select and select case msgs: = < – CHS.

range

Data can be read from the channel continuously until the channel is closed. When there is no data in the channel, the current goroutine will be blocked, which is the same as the blocking processing mechanism when reading the channel.

package main

import (
   "fmt"
   "time"
)

func goRoutineD(ch chan int, i int) {
   for   i := 1; i <= 5; i++{
      ch <- i
   }

}
func chanRange(chanName chan int) {
   for e := range chanName {
      fmt.Printf("Get element from chan: %d\n", e)
      If len (channame) < = 0 {// if the existing data amount is 0, jump out of the loop
            break
      }
   }
}
func main() {
   ch := make(chan int, 5)
   go goRoutineD(ch, 5)
   chanRange(ch)

}

result:
Get element from chan: 1
Get element from chan: 2
Get element from chan: 3
Get element from chan: 4
Get element from chan: 5

Deadlock

It refers to a blocking phenomenon caused by competing resources or communicating with each other during the execution of two or more coprocesses.

In a non buffered channel, if only inflow and no outflow occurs, or only outflow does not inflow, deadlock will occur.

Here are some examples of deadlocks

1、

package main

func main() {
   ch := make(chan int)
   ch <- 3
}

In the above situation, writing data to a non buffered channel will cause a deadlock. Solution create buffer ch: = make (Chan int, 3)

2、

package main

import (
   "fmt"
)

func main() {
   ch := make(chan int)
   fmt.Println(<-ch)
}

Reading data to a non buffered channel can cause a deadlock. Solution: open the buffer and write data to the channel first.

3、

package main

func main() {
   ch := make(chan int, 3)
   ch <- 3
   ch <- 4
   ch <- 5
   ch <- 6
}

A deadlock can also occur if the number of writes exceeds the number of buffers. The solution is to remove the written data.

There are many cases of deadlock, which will not be repeated here.
In another case, writing data to a closed channel will not result in deadlock and create a panic.

package main

func main() {
    ch := make(chan int, 3)
    ch <- 1
    close(ch)
    ch <- 2
}

The solution is not to write data to a closed channel.

reference resources:

https://codeburst.io/diving-d…

https://speakerdeck.com/kavya…

https://my.oschina.net/renhc/…

Recommended Today

PHP 12th week function learning record

sha1() effect sha1()Function to evaluate the value of a stringSHA-1Hash. usage sha1(string,raw) case <?php $str = “Hello”; echo sha1($str); ?> result f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0 sha1_file() effect sha1_file()Function calculation fileSHA-1Hash. usage sha1_file(file,raw) case <?php $filename = “test.txt”; $sha1file = sha1_file($filename); echo $sha1file; ?> result aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d similar_text() effect similar_text()Function to calculate the similarity between two strings. usage similar_text(string1,string2,percent) case […]