Go channel and sync package sharing

Time:2022-6-20

[TOC]

Go channel and sync package sharing

Let’s review what we shared last time:

  • If the go synchronization is not restricted, it will generateData raceQuestion of
  • We use locks to solve the above problems. We choose to use mutex locks and read-write locks according to the usage scenarios
  • A better way to use locks than atomic operations, but with gosync/atomicUse with care because memory is involved

If you are still interested in go locks and atomic operations, please check out the articleGo lock and atomic operation sharing

Last time, we shared locks and atomic operations, which can ensure the reading and writing of shared data

However, they still affect performance, but go providespassagewayThis artifact

Today, let’s share other synchronization methods recommended in go,Channels and sync packets

Go channel and sync package sharing

What is the passage?

Is a special type of connection concurrencygoroutinePipe for

The channel channel allows one goroutine process to send a specific value to another goroutine processCommunication mechanism

passagewayLike a conveyor belt or queue, always followFirst in first out(first in first out) to ensure the order of sending and receiving data, which is consistent withThe ConduitIt’s the same

One co process puts data from one end of the channel, and the other co process reads data from the other end of the channel

Each channel is a conduit of a specific type. When declaring a channel, you need to specify its element type.

What can channels do?

Control the synchronization of the coordination process to make the program run orderly

Advocated in goDo not communicate through shared memory, but share memory through communication

The goroutine cooperation process is the concurrent execution body of the Go program. The channel channel is the connection between them, the bridge between them, and their transportation hub

What are the channels?

It can be roughly divided into the following three types:

Go channel and sync package sharing

  • Unbuffered channel
  • Buffered channel
  • Unidirectional channel

Unbuffered channel

Go channel and sync package sharing

Unbuffered channels are also called blocked channels

The sending operation on the unbuffered channel will be blocked until another goroutine performs the receiving operation on the channel. At this time, the value can be successfully sent

The two goroutine processes will continue to be executed

On the contrary, if the receive operation is performed first, the goroutine of the receiver will block until another goroutine process sends a data on the channel

Therefore, unbuffered channels are also calledSynchronous channel, because we can use unbuffered channels for communication, and use the goroutine protocol for sending and receivingSynchronization

Buffered channel

Go channel and sync package sharing

As mentioned above, there is a buffer channel, which is used to initialize / create the channelMake functionOf2Fill in the expected buffer size with parameters, for example:

ch1 := make(chan int , 4)

At this time, the capacity of the channel is 4. The sender can send data to the channel until the channel is full and the channel data is not read away. The sender will block

As long as the capacity of a channel is greater than zero, the channel is a buffered channel

The capacity of a channel indicates the number of elements that can be stored in the channel

We can use the built-inLen functionGet the number of elements in the channel, usingCap functionGet the capacity of the channel

Unidirectional channel

By default, channels can be read or written, but one-way channels can only read or write

  • chan <- int

Go channel and sync package sharing

It is a channel that can only be sent. It can be sent but cannot be received

  • <- chan int

Go channel and sync package sharing

It is a channel that can only receive but cannot send

How to create and declare a channel

Claim channel

In go, channel is a type. By default, it is a reference type

Briefly explain what is a reference:

When we write c++, we use more references

Reference, as the name suggests, is an alias of a variable or object. The operation on the reference is completely equivalent to the operation on the bound variable or object

It is used in c++

Type & reference name = target variable name;

Declare a channel

Var variable name Chan element type

Var ch1 Chan string // declare a channel that passes string data
Var CH2 Chan []int // declare a channel that passes int slice data
Var CH3 Chan bool // declare a channel that passes Boolean data
Var CH4 Chan interface{} // declare a channel that passes interface type data

Look, it’s that simple to declare a channel

For a channel, it is declared that it cannot be used. The declared channel defaults to the zero value of its corresponding type, for example

  • Zero value of int type is 0
  • Zero value of string type is an empty string
  • Bool type zero value is false
  • The zero value of the slice is nil

We also need to initialize the channel before we can use the channel normally

Initialize channel

Generally usedMake functionThe channel can only be used after initialization, or it can be used directlyMake functionCreate channel

For example:

ch5 := make(chan string)
ch6 := make(chan []int)
ch7 := make(chan bool)
ch8 := make(chan interface{})

Make functionThe second parameter of can set the buffer size. Let’s take a look at the description of the source code

// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
// Slice: The size specifies the length. The capacity of the slice is
// equal to its length. A second integer argument may be provided to
// specify a different capacity; it must be no smaller than the
// length. For example, make([]int, 0, 10) allocates an underlying array
// of size 10 and returns a slice of length 0 and capacity 10 that is
// backed by this underlying array.
// Map: An empty map is allocated with enough space to hold the
// specified number of elements. The size may be omitted, in which case
// a small starting size is allocated.
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type

IfMake functionIf the second parameter of is not filled in, the default channel is unbuffered

Now let’s see how to operate the channel channel and how to play it

Go channel and sync package sharing

How to operate channel

There are three operations for the channel:

  • Send
  • Receive
  • Close

For the data in the sending and receiving channels, the writing method is more vivid. Use<-To point to whether to read data from the channel or send data from the channel

Send data to channel

//Create a channel
ch := make(chan int)
//Send data to channel
ch <- 1

We see that the direction of the arrow is that 1 points to the ch channel, so it is not difficult to understand. This is to put the data 1 into the channel

Receive data from channel

num := <-ch

It is not difficult to see that the above code is that ch points to a variable that needs to be initialized, that is, read a data from CH and assign it to num

We can read the data from the channel, or we can ignore it without assignment, such as:

<-ch

Close channel

The close function is provided in go to close the channel

close(ch)

It is very important to pay attention to closing the channel. Improper use will directly lead to program crash

Go channel and sync package sharing

  • The channel needs to be closed only when the receiver is notified that all data in the goroutine process has been sent
  • The channel can be recycled by the garbage collection mechanism. It is different from closing the file. Closing the file after the operation is necessary, but closing the channel is not necessary

The closed channel has the following4Features:

  • Yes, oneClosed channelSending the value again will causepanic
  • Yes, oneClosed channelReceive will get the value until the channel is empty
  • Yes, oneClosed channel with no valueThe corresponding type ofZero value
  • Closing a closed channel will cause panic

Sorting out channel abnormalities

Let’s sort out the exceptions for the channel:

Channel status Uninitialized channel (NIL) Channel not empty Channel is empty The passage is full Channel not full
receive data block receive data block receive data receive data
send data block send data send data block send data
close panic Channel closed successfully
After data reading
Return zero value
Channel closed successfully
Return zero directly
Channel closed successfully
After data reading
Return zero value
Channel closed successfully
After data reading
Return zero value

Demo practice of each channel

Unbuffered channel

func main() {
   //Create an unbuffered channel with data type of int
   ch := make(chan int)
   //Write the number 1 to the channel
   ch <- 1
   fmt.Println("send successfully ... ")
}

We can see the effect by executing the above code

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        F:/my_channel/main.go:9 +0x45
exit status 2

The above error is reporteddeadlockThe reason for the error should be known by careful partners. I mentioned above

Go channel and sync package sharing

We usech := make(chan int)Unbuffered channel created

Unbuffered channels can send data successfully only when the receiver receives the value

We can think of the cases in our life:

You bought a slightly more expensive item in a certain East. When a courier in a certain East sends you an express, he calls you and must deliver it to you. Otherwise, he dare not sign for it. At this time, it is inconvenient for you or you do not sign for it. Then the express is counted as unsuccessful

Therefore, the reason for the above problem is that a non buffered channel is created, the sender is blocking all the time, and there is no co process to read data in the channel, resulting in deadlock

Our solution is to create another collaboration to read the data from the channel

package main

import "fmt"

func recvData(c chan int) {
    ret := <-c
    fmt.Println("recvData successfully ... data = ", ret)
}

func main() {
    //Create an unbuffered channel with data type of int
    ch := make(chan int)
    go recvData(ch)
    //Write the number 1 to the channel
    ch <- 1
    fmt.Println("send successfully ... ")
}

Note here that ifgo recvData(ch)It’s onch <- 1After that, the result is the same deadlock. The reason isch <- 1Will block all the time, and will not execute the statements after him at all

Go channel and sync package sharing

Actual effect

recvData successfully ... data =  1
send successfully ...

Buffered channel

func main() {
   //Create an unbuffered channel with data type of int
   ch := make(chan int , 1)
   //Write the number 1 to the channel
   ch <- 1
   fmt.Println("send successfully ... ")
}

In the same case and the same code, we just replaced the unbuffered channel with the buffered channel, and we still do not specifically open the cooperation process to read the channel data

Actual effect, sent successfully

$$

$$

send successfully ...

Since the buffer in the channel is 1 at this time, the data sent to the channel for the first time will not be blocked,

However, if data is written to the channel before the data in the channel is read out, it will be blocked here,

If there is no coroutine to read data from the channel all the time, the result is the same as the above, and deadlock will occur

Unidirectional channel

package main

import "fmt"

func OnlyWriteData(out chan<- int) {
   //One way channel, write only but not read
   for i := 0; i < 10; i++ {
      out <- i
   }
   close(out)
}

func CalData(out chan<- int, in <-chan int) {
   //Out one way channel, write only but not read
   //Int one-way channel, read-only, not write

   //Traverse and read the in channel. If the in channel data is read, it will be blocked. If the in channel is closed, it will exit the cycle
   for i := range in {
      out <- i + i
   }
   close(out)
}
func myPrinter(in <-chan int) {
   //Traverse and read the in channel. If the in channel data is read, it will be blocked. If the in channel is closed, it will exit the cycle
   for i := range in {
      fmt.Println(i)
   }
}

func main() {
   //Create 2 unbuffered channels
   ch1 := make(chan int)
   ch2 := make(chan int)


   go OnlyWriteData(ch1)
   go CalData(ch2, ch1)


   myPrinter(ch2)
}

We simulate two channels,

  • One can only write but not read
  • A read-only cannot be written

Actual effect

0
2
4
6
8
10
12
14
16
18

Close channel

package main

import "fmt"

func main() {
   c := make(chan int)
   
   go func() {
      for i := 0; i < 10; i++ {
         //Write data to the unbuffered channel circularly. Only after the previous data is read can the next data be put into the channel
         c <- i
      }
      //Close channel
      close(c)
   }()
   for {
      //Read the data in the channel. If there is no data in the channel, it will be blocked. If ok is read as false, the channel will be closed and the cycle will exit
      if data, ok := <-c; ok {
         fmt.Println(data)
      } else {
         break
      }
   }
   fmt.Println("channel over")
}

Once again, close the channel. The simulation method of the demo is basically the same as that of the above case. Those interested can run it to see the effect

Go channel and sync package sharing

After seeing this, the careful partner should be able to conclude whether the channel is closed2 kindsWay?

  • When reading the channel, judge whether the variable of bool type is false

For example, the above code

if data, ok := <-c; ok {
    fmt.Println(data)
} else {
    break
}

If ok is judged to be true, the data will be read normally. If false, the channel will be closed

  • Traverse the channel by means of for range. If you exit the loop, it is because the channel is closed

Go channel and sync package sharing

Sync package

GoSync packageAlso used to synchronize concurrent tasks

Remember, sharing articlesGo lock and atomic operation sharingWe used it whenSync package

The usage is the same as the message. Here are some examplesSync packageData structures and methods involved

  • sync.WaitGroup
  • sync.Once
  • sync.Map

sync.WaitGroup

It is a structure, and the pointer should be passed when passing. Here, you need to pay attention to

It is concurrent and safe. A counter is maintained internally

Methods involved:

  • (wg * WaitGroup) Add(delta int)

The delta passed in the parameter indicates sync Waitgroup internal counter + delta

  • (wg *WaitGroup) Done()

Indicates that the current collaboration exits, counter -1

  • (wg *WaitGroup) Wait()

Wait until the execution of the concurrent task is completed. At this time, the counter becomes 0

sync.Once

It is concurrent and secure. There are mutexes and a boolean type of data inside

  • Mutex is used for locking and unlocking
  • Boolean data is used to record whether initialization is completed

It is generally used to execute only once in high concurrency scenarios. The scenarios we can think of at once include the scenario of loading the configuration file when the program starts

For similar scenarios, go also provides us with solutions, namelysync. Do method in once

  • func (o *Once) Do(f func()) {}

The parameter of the do method is a function, but how can we pass the parameter in this function?

Go channel and sync package sharing

You can use theclosureTo realize the specific implementation of closures. Those interested can have an in-depth understanding

sync.Map

He isConcurrency security, because the map in go is unsafe for concurrency, sosync.Map

sync. Map has the following obvious advantages:

  • Concurrency security
  • sync. Map does not need to be initialized with make, but can be used directlymyMap := sync.Map{}Sync Methods in map

sync. Methods involved in map

See the name and know the meaning

  • Store

Save key and value

  • Load

Get the value corresponding to a key

  • LoadOrStore

Take out and save 2 operations

  • Delete

Delete key and corresponding value

  • Range

Traverse all keys and corresponding values

summary

  • What is the channel and the type of channel
  • No buffer, with buffer, what does the unidirectional channel correspond to
  • Specific practices for access
  • Shared the sorting of channel exceptions
  • Simply shared the use of sync package

Welcome to like, follow and collect

My friends, your support and encouragement are my motivation to insist on sharing and improve quality

Go channel and sync package sharing

OK, that’s all for this time,Etcd for next service registration and discovery

Technology is open, and our mentality should be open. Embrace change, rise to the sun, and strive to move forward.

I amLittle Devil boy Nezha, welcome to like and follow the collection, see you next time~