[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 go
sync/atomic
Use 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
What is the passage?
Is a special type of connection concurrencygoroutine
Pipe 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:
- 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 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
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
It is a channel that can only be sent. It can be sent but cannot be received
- <- chan int
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
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
- 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
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 <- 1
After that, the result is the same deadlock. The reason isch <- 1
Will block all the time, and will not execute the statements after him at all
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
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
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?
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 directly
myMap := 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
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~