Preface
In business, we often need to trigger various functions based on timing tasks. For example, TTL session management, locking, timing tasks (alarm clock) or more complex state switching, etc. Baina.com mainly introduces relevant content to everyone about the Golang timer trap. The so-called trap is that it is not what you think. This cognitive error may leave hidden bugs on your software. Timer has 3 traps, we will talk about it
1) Reset's traps and
2) Traps of the channel,
3) Stop's trap is similar to Reset's trap, explore it yourself.
I won't say much below, let's take a look at the detailed introduction
Where is the trap of Reset
()
The return value of the function is bool type. Let's look at a three-in-one problem:
- What does its return value represent?
- What is the success we want?
- What is failure?
Success: After a period of time, the timer timed out and a timeout event was received.
Failure: The opposite of success, we cannot receive that event. For failure, we should do something to make sure our timer works.
Does Reset return value mean this?
By viewing the documentation and implementing()
The return value does not meet our expectations, this is the error. Its return value does not mean that the reset timer is successful or failed, but rather expresses the timer's status before reset:
- Return false when Timer has stopped or timed out.
- Return true when the timer does not time out.
So, when Reset returns false, we cannot think that the timeout will not come after a period of time, but may actually come and the timer has already taken effect.
Skip the trap and encounter it again
How to skip the previous traps and make Reset meet our expected functions? It is better to ignore the return value of Reset directly, it will not help you achieve the expected results.
The real trap is the Timer channel, which is closely related to our expected success and failure. The timer we expect fails to set, which is usually only related to the channel: whether there is data in the channel of the timer before setting the timer.
- If there is, the timer we set fails and we may read an incorrect timeout event.
- If not, the timer we set succeeds and we get the timeout event at the set time.
Next, why failure is only related to whether there is a timeout event in the channel.
The cache channel size of the timer is only 1, and the timeout event cannot be stored for more timeout events, please see the source code.
// NewTimer creates a new Timer that will send // the current time on its channel after at least duration d. func NewTimer(d Duration) *Timer { c := make(chan Time, 1) // The cache channel size is 1 t := &Timer{ C: c, r: runtimeTimer{ when: when(d), f: sendTime, arg: c, }, } startTimer(&) return t }
The timer is created and runs separately. After the timeout, data will be written to the channel, and you read the data from the channel. The current timeout data is not read, but a new timer is set, and then the data is read in the channel. As a result, what is read is the timeout event of the last timeout. It seems to be successful, but it actually fails and completely falls into the trap.
Cross traps and ensure success
If guaranteed()
Success, get the results we want?()
Clear the channel before.
When the business scenario is simple, there is no need to actively clear the channel. For example, the processing flow is: set a timer once, process a timer once, without interruption in the middle, and before the next Reset, the channel must be empty.
When the business scenario is complex and you are not sure whether the channel is empty, then you will actively clear it.
if len() > 0{ <- } ()
Test code
package main import ( "fmt" "time" ) // In different cases, the return value of ()func test1() { ("Test 1: What does the Reset return value have to do?") tm := () defer () quit := make(chan bool) // Exit event go func() { (3 * ) quit <- true }() // Timer has not timed out, see the return value of Reset if !() { ("Didn't timeout, Reset returns false") } else { ("Didn't timeout, Reset returns true") } // Stop timer () if !() { ("Stop Timer, Reset returns false") } else { ("Stop Timer, Reset returns true") } // Timer timeout for { select { case <-quit: return case <-: if !() { ("Timeout, Reset returns false") } else { ("Timeout, Reset returns true") } } } } func test2() { ("\nTest 2nd test: After timeout, can the event in the channel be successfully read without reading it?") sm2Start := () tm2 := () (2 * ) ("Number of events in the channel before Reset:%d\n", len()) if !() { ("Reset returns false if not read channel data") } else { ("Reset returns true if not read channel data") } ("Number of events in the channel after Reset:%d\n", len()) select { case t := <-: ("tm2 start time: %v\n", ()) ("Time of event in channel: %v\n", ()) if (sm2Start) <= + { ("The time in the channel is the time before resetting sm2, that is, the time when the first timeout is reached, so the second Reset failed") } } ("After reading the channel, the number of events in it:%d\n", len()) () ("After Reset again, the number of events in the channel:%d\n", len()) (2 * ) ("Number of events in the channel after timeout:%d\n", len()) } func test3() { ("\nTest 3: Clear the channel before Reset, as smooth as possible") smStart := () tm := () (2 * ) if len() > 0 { <- } () // time out t := <- ("tm start time: %v\n", ()) ("Time of event in channel: %v\n", ()) if (smStart) <= + { ("The time in the channel is the time before resetting sm, that is, the time when the first timeout is released, so the second Reset failed") } else { ("The time in the channel is the time after resetting sm, and Reset succeeded") } } func main() { test1() test2() test3() }
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.