Conditional variables for GO
1. Conditional variables and mutex locks
- Conditional variables are based on mutexes, which must be based on mutexes to function;
- Condition variables are not used to protect critical areas and shared resources, they are used to coordinate those threads that want to access shared resources;
- In Go, the biggest advantage of conditional variables is the improvement in efficiency. When the shared resource does not meet the conditions, the thread that wants to operate it does not need to check it round-trip, just wait for notification.
2. The use of conditional variables and mutex locks
The initialization of conditional variables is inseparable from mutex locks, and its method is somewhat based on mutex locks.
There are three methods provided by condition variables: waiting notification (wait), single notification (signal), and broad notification (broadcast).
III. Use of conditional variables
(1) Create locks and conditions
// mailbox represents mailbox // 0 means that the mailbox is empty, 1 means that the mailbox is full var mailbox uint8 // lock represents the lock on the mailbox var lock // sendCond represents a condition variable dedicated to sending a message var sendCond = (&lock) // reveCond represents a condition variable dedicated to receiving letters var reveCond = (())
-
Types are not available out of the box, only available
Creates its pointer value. This function requires
Parameter value of type.
-
is an interface that contains two pointer methods, i.e.
Lock()
andUnlock()
;therefore,and
The pointer types of these two types are
The implementation type of the interface.
- The above lock variable Lock method and Unlock method are used to lock and unlock the write lock in it, respectively, and they correspond to the meaning of the sendCond variable.
-
()
The obtained value has Lock and Unlock methods, and the RLock method and RUnlock method of the lock variable will be called internally;
(2) Use
() for mailbox == 1 { () } mailbox = 1 () ()
() for mailbox == 0 { () } mailbox = 0 () ()
Complete code:
package main import ( "log" "sync" "time" ) func main() { // mailbox represents mailbox // 0 means that the mailbox is empty, 1 means that the mailbox is full var mailbox uint8 // lock represents the lock on the mailbox var lock // sendCond represents a condition variable dedicated to sending a message var sendCond = (&lock) // reveCond represents a condition variable dedicated to receiving letters var reveCond = (()) // sign is used to deliver the demo completed signal sign := make(chan struct{}, 2) max := 5 go func(max int) { // Used to send messages defer func() { sign <- struct{}{} }() for i := 1; i <= max; i++ { ( * 5) () for mailbox == 1 { () } ("sender [%d]: the mailbox is empty.", i) mailbox = 1 ("sender [%d]: the letter has been sent.", i) () () } }(max) go func(max int) { // Used for receiving letters defer func() { sign <- struct{}{} }() for j := 1; j <= max; j++ { ( * 500) () for mailbox == 0 { () } ("receiver [%d]: the mailbox is full.", j) mailbox = 0 ("receiver [%d]: the letter has been received.", j) () () } }(max) <-sign <-sign }
4. What does the Wait method of conditional variables do?
(1) Four main things that the condition variable Wait method does
The Wait method of conditional variables mainly does four things:
- Add the goroutine (that is, the current goroutine) that calls it to the notification queue of the current condition variable;
- Unlock the mutex on which the current condition variable is based;
- Let the current goroutine be in a waiting state and decide whether to wake it up when the notification arrives. At this time, this goroutine will block on the line of code that calls this Wait method;
- If the notification arrives and decides to wake up the goroutine, then re-lock the mutex on which the current condition variable is based. From then on, the current goroutine will continue to execute the following code.
(2) Why do we need to lock the mutex based on the condition variable before we can call its wait method
Because the wait method of the condition variable unlocks the mutex based on before blocking the current goroutine. Therefore, before calling the wait method, the mutex must be locked first, otherwise an irrecoverable panic will be triggered when the wait method is called.
If the Wait method of the condition variable does not unlock the mutex first, it will cause two consequences: either the current program crashes due to panic, or the related goroutine is blocked completely.
(3) Why use the for statement to wrap the called wait method expression, can't use the if statement?
The if statement will only check the status of the shared resource once, while the for statement can do multiple checks until the status changes.
The reason for multiple inspections is mainly to be on the safe side. If a goroutine is awakened because it receives a notification, but finds that the status of the shared resource still does not meet its requirements, then the Wait method of the condition variable should be called again and continue to wait for the next notification to arrive.
This is very likely to happen, as shown below:
- There are multiple goroutines waiting for the same state of shared resources. For example, they are waiting for the mailbox variable to change its value to 0 when the value of the mailbox is not 0, which is equivalent to multiple people waiting for me to place information into the mailbox. Although there are multiple goroutines waiting for, there can only be one goroutine for each successful one. Don't forget that the Wait method of the condition variable will re-lock the mutex after the current goroutine wakes up. After the successful goroutine finally unlocks the mutex, other goroutines will enter the critical zone one after another, but they will find that the state of the shared resource is still not what they want. At this time, the for loop is very necessary.
- A shared resource may have a state that is not two, but more. For example, the possible values of mailbox variables are not only 0 and 1, but also 2, 3, and 4. In this case, since there may only be one result after each change of state, a single result will definitely not be able to meet all the conditions of goroutines under the premise of reasonable design. Those unmet goroutines obviously need to continue waiting and checking.
- There is a possibility that there are only two states of shared resources, and each state has only one goroutine to focus on, just like the example we implemented in the main problem. Even so, though, it is still necessary to use the for statement. The reason is that in some multi-CPU core computer systems, even if the condition variable is not notified, the goroutine that calls its Wait method may be awakened. This is determined by the computer hardware level, even conditional variables provided by the operating system (such as Linux) themselves.
To sum up, when wrapping the Wait method of condition variables, we should always use the for statement.
Don't use if statements, because it cannot perform the "check status - wait for notification - wake up" process repeatedly.
(4) Signal method and Broadcast method of condition variables
The condition variable signal method and the Broadcast method are both used to send notifications. The difference is that the former notification only wakes up a goroutine that is waiting for, while the latter notification wakes up all goroutines that are waiting for this.
The Wait method of the condition variable always adds the current goroutine to the end of the notification queue, and its Signal method always starts from the beginning of the notification queue, looking for a wake-up goroutine. Therefore, due to the notification of the Signal method, the awakened goroutine is usually the first one to wait for.
Where the condition variables Signal method and Broadcast method are placed:
Unlike the Wait method, the Signal method and Broadcast method of condition variables do not need to be executed under the protection of mutex locks. On the contrary, we'd better call these two methods after unlocking the mutex on which the condition variable is based. This is more conducive to the operation efficiency of the program.
Notification of condition variables is immediate:
If there is no goroutine waiting for this when sending the notification, the notification will be discarded directly. The goroutine that only started waiting after this could only be awakened by the subsequent notification.
This is the end of this article about the condition variables of GO. For more related condition variables, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!