directory

  • What is the sync. Once
  • How do I use sync.once
  • Source code analysis

The article number originated in the public mp.weixin.qq.com/s/b89PmljEL Michael mo coding 】 【…

What is the sync. Once

Once can be used to perform an action only Once and is often used in singleton initialization scenarios.

Once is often used to initialize a singleton resource, or to concurrently access a shared resource that only needs to be initialized Once, or to initialize a test resource at test time.

Sync.once exposes only one method, Do. You can call the Do method multiple times, but the f argument is executed only on the first call to the Do method, where f is a no-argument, no-return function.

How do I use sync.once

For example, one project I was working on needed to get the resource configuration when the project started because the configuration was hung on a third-party platform. We needed a way to ensure that the configuration was only fetched Once, so we considered using sync.once to get the resource. This prevents the get resource method from being called elsewhere, which is executed only once.

Let me write a simple Demo to show how sync.once works

package main
import (
   "fmt"
   "sync"
)
var once sync.Once
var con string
func main(a) {
   once.Do(func(a) {
      con = "hello Test once.Do"
   })
   fmt.Println(con)
}
Copy the code

Code description:

Sync.once (hello Test once.Do) is used to print the value of the string (” Hello Test once.Do”) to con.

However, when we use a method or framework, if we don’t know everything about it, it is always a little unreliable and we feel uneasy. To that end, let’s talk about sync.once’s source code implementation, leaving it nowhere to hide.

Source code analysis

Sync.do is stored in the package sync once. Go file. The source code is as follows:

// sync/once.go

type Once struct {
   done uint32 // An initial value of 0 indicates that it has not been executed, and 1 indicates that it has been executed
   m    Mutex 
}
func (o *Once) Do(f func(a)) {
   // Check whether done is 0, if 0, it has not been executed, call doSlow() to initialize
   if atomic.LoadUint32(&o.done) == 0 {
      // Outlined slow-path to allow inlining of the fast-path.
      o.doSlow(f)
   }
}

// Load resources
func (o *Once) doSlow(f func(a)) {
   o.m.Lock()
   defer o.m.Unlock()
   // Check whether done is zero
   if o.done == 0 {
      // After executing f(), set done to 1
      defer atomic.StoreUint32(&o.done, 1)
      // Executes the passed f() function
      f()
   }
}
Copy the code

The next part will be divided into two parts for analysis. The first part is the structure of Once, and the second part is the realization principle of Do function. I will add comments on the code to ensure that I can get harvest after reading carefully.

The structure of the body

type Once struct {
   done uint32 // An initial value of 0 indicates that it has not been executed, and 1 indicates that it has been executed
   m    Mutex 
}
Copy the code

We define a struct Once that stores two member variables: done and m.

Done member variable

  • 1 indicates that the resource is not initialized and needs to be further initialized
  • 0 indicates that the resource is initialized

M member variable

  • To prevent the resource from being initialized multiple times when multiple Goroutines call doSlow() to initialize it, a Mutex lock mechanism is used to ensure that the resource is initialized only once

Do

func (o *Once) Do(f func(a)) {
   // Check whether done is 0, if 0, it has not been executed, call doSlow() to initialize
   if atomic.LoadUint32(&o.done) == 0 {
      // Outlined slow-path to allow inlining of the fast-path.
      o.doSlow(f)
   }
}

// Load resources
func (o *Once) doSlow(f func(a)) {
   o.m.Lock()
   defer o.m.Unlock()
   // Check whether done is zero
   if o.done == 0 {
      // After executing f(), set done to 1
      defer atomic.StoreUint32(&o.done, 1)
      // Executes the passed f() function
      f()
   }
Copy the code

When calling the Do function, check whether the done value is 0. If the value is 1, it indicates that the anonymous function f() passed in has been executed and does not need to be executed again. If 0, the passed anonymous function f() has not yet been executed, and the doSlow() function is called to initialize.

In the doSlow() function, if a concurrent goroutine enters the function, to ensure that only one goroutine executes the f() anonymous function. Double-checking is used to check whether O.tone is 0. If o.tone is 0, then o.Tone is set to 1. Then the lock is released.

Even if more than one Goroutine enters the doSlow method at the same time, subsequent goroutines will see the value of O.Tone as 1 and will not execute f again because of the double-checking mechanism.

This ensures that concurrent Goroutines will wait for F to complete without executing f more than once.

The article will be updated continuously. You can search “Maimo Coding” on wechat to read it for the first time. Every day to share quality articles, big factory experience, big factory face, help interview, is worth paying attention to every programmer platform.