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~