Sharing of GO channel and sync package

Posted by artyfarty on Tue, 01 Feb 2022 18:34:52 +0100

[TOC]

Sharing of GO channel and sync package

Let's review what we shared last time:

  • If there are no restrictions on GO synchronization, data race will occur
  • We use locks to solve the above problems. We choose to use mutex locks and read-write locks according to the usage scenario
  • A better way than using locks is atomic operations, but using go's sync/atomic needs to be used with caution because it involves memory

If you are still interested in GO locks and atomic operations, please check out the article GO 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 will still affect performance, but Go provides an artifact for developing this channel

Today, let's share other synchronization methods, channels and sync packages recommended in Go

What is the passage?

Is a special type of pipe connecting concurrent goroutine

channel is a communication mechanism that allows one goroutine process to send a specific value to another goroutine process.

The channel is like a conveyor belt or queue. It always follows the First In First Out rule to ensure the order of sending and receiving data, which is the same as that of the pipeline

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 cooperation process to make the program run in order

GO advocates not to communicate through shared memory, but to share memory through communication

goroutine cooperation process is the concurrent execution body of Go program. 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:

  • Unbuffered channel
  • Buffered channel
  • Unidirectional channel

Unbuffered channel

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 not be sent successfully

The two goroutine processes will continue to be executed

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

Therefore, the unbuffered channel is also called the synchronization channel, because we can use the unbuffered channel for communication and synchronize by using the goroutine process of sending and receiving

Buffered channel

As mentioned above, there is a buffer channel, that is, fill the second parameter of the make function of initializing / creating the channel with the expected buffer size, for example:

ch1 := make(chan int , 4)

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

As long as the capacity of the 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-in len function to obtain the number of elements in the channel and the cap function to obtain 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

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

  • <- chan int

It is a channel that can only receive, but can not send

How to create and declare a channel

Declaration channel

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

Briefly explain what a reference is:

When we write C + +, we use more references

Reference, as the name suggests, is the 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               // Declares 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 so simple to declare a channel

For a channel, it cannot be used after being declared closed. The declared channel defaults to the zero value of its corresponding type, such as

  • The zero value of int type is 0
  • A 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, channels can only be used after initialization with the make function. You can also directly use the make function to create channels

For example:

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

The second parameter of the make function can set the size of the buffer. 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

If the second parameter of the make function is not filled in, it defaults to an unbuffered channel

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

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 of 1 into the channel

Receive data from channel

num := <-ch

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

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

<-ch

Close channel

Go provides the close function to close the channel

close(ch)

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

  • 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 following four characteristics:

  • Sending a value to a closed channel will result in panic
  • Receiving a closed channel will get the value until the channel is empty
  • Performing a receive operation on a closed channel with no value will result in a corresponding type of zero value
  • Closing a closed channel will cause panic

Sorting out channel abnormalities

Let's sort out the exceptions that will exist for the channel:

channel status Uninitialized channel (nil) Channel not empty The 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
Returns a zero value
Channel closed successfully
Return zero directly
Channel closed successfully
After data reading
Returns a zero value
Channel closed successfully
After data reading
Returns a zero value

DEMO actual combat of each channel

Unbuffered channel

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

By executing the above code, we can see the effect

fatal error: all goroutines are asleep - deadlock!

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

The reason for the above error reporting deadlock error should be known by careful partners. I mentioned above

We use ch: = make (Chan int) to create an unbuffered channel

The unbuffered channel can send data successfully only when the receiver receives the value

We can think of the case in our life:

You bought a slightly more valuable item in a certain East. When a certain East courier sent you an express, he called you and had to deliver it to you, otherwise he didn't dare to sign for it. At this time, if it's inconvenient for you or you don't sign for it, the express was counted as unsuccessful

Therefore, the reason for the above problem is that a bufferless channel is created, the sender has been blocking, and there has been no co process to read data in the channel, resulting in deadlock

Another solution is to read the data out of the process

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 int
    ch := make(chan int)
    go recvData(ch)
    // Write the number 1 to the channel
    ch <- 1
    fmt.Println("send successfully ... ")
}

It should be noted here that if go recvData(ch) is placed after Ch < - 1, the result will still be the same deadlock. The reason is that ch < - 1 will be blocked all the time and the statements after it will not be executed at all

Actual effect

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

Buffered channel

func main() {
   // Create an unbuffered channel with data type 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 replace the non buffered channel with the buffered channel. We still don't specifically open the cooperation process to read the data of the channel

Actual effect, sent successfully
$$

$$

send successfully ...

Because the buffer in the channel is 1 at this time, sending data to the channel for the first time will not block,

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 co process to read data from the channel all the time, the result is the same as above and will deadlock

Unidirectional channel

package main

import "fmt"

func OnlyWriteData(out chan<- int) {
   // One way channel, write only, 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, not read
   // int one-way channel, read-only, not writable

   // 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++ {
         // Loop writes data to the unbuffered channel. 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 exit the cycle
      if data, ok := <-c; ok {
         fmt.Println(data)
      } else {
         break
      }
   }
   fmt.Println("channel over")
}

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

See here, the careful little partner should be able to sum up two ways to judge whether the channel is closed?

  • 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

sync package

Go's sync package is also used to synchronize concurrent tasks

Remember, sharing articles GO lock and atomic operation sharing We used the sync package when we were

The usage is the same as message. Here are the data structures and methods involved in sync package

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

sync.WaitGroup

It is a structure. When passing, you need to pass the pointer. Here you need to pay attention

It is concurrent and safe, and 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 concurrent tasks is completed. At this time, the counter becomes 0

sync.Once

It is concurrency safe, with mutex and Boolean data inside

  • Mutex locks are used to unlock locks
  • 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 a solution, namely sync Do method in once

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

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

You can use the closure in Go to realize the specific implementation method of closure. Those who are interested can have an in-depth understanding

sync.Map

It is concurrency safe. It is precisely because the map in Go is concurrency unsafe, so there is sync Map

sync.Map has the following obvious advantages:

  • Concurrent security
  • sync.Map does not need to be initialized with make, but directly uses mymap: = sync Map {} to use 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 store 2 operations

  • Delete

Delete key and corresponding value

  • Range

Traverse all key s and corresponding value s

summary

  • What is the channel and the type of channel
  • No buffer, with buffer, what does one-way channel correspond to
  • Specific practice of channel
  • Shared the sorting of abnormal conditions of the channel
  • The use of sync package is simply shared

Welcome to like, follow and collect

My friends, your support and encouragement are the driving force for me to insist on sharing and improve quality

Well, that's all for this time. The next ETCD of service registration and discovery

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

I'm Nezha, the Little Devil boy. Welcome to praise and pay attention to the collection. See you next time~

Topics: Go