Classmate, is your single case enough for the interview?

After answering the interviewer's GMP related questions with great difficulty, let's go to the single case related questions. Although the single case is simple, the interviewer is also in-depth layer by layer, making chao chao sweating. Let's take a look at what the single case interviewer asked.

Recognize single case

Interviewer: you know that the recycle bin in mac can only be opened separately, but can the access window be opened more?

Test site: advantages and disadvantages of single case use scenario

Chao chao: I know. This should be a singleton mode. There is no need to use two wastebaskets in our daily work, and the resources between wastebaskets are shared, so there is no need to open more wasteful system resources.

How to use single example

Interviewer: you just talked about single cases. Do you know how to use single cases in go?

Test site: sync Once use

Chaochao: This is simple. For example, when we build a project, because the configuration information is shared globally, we will make the object reading the configuration information into a single instance.

package main

import (
  "fmt"
  "sync"
)

//Assume that only the server id is in the configuration information
type Config struct {
  id int
}

var (
  once   sync.Once
  config *Config
)

func (p *Config) GetID() int {
  return p.id
}

func getConfig() *Config {
  //There are two locks in the bottom layer to prevent f from not executing completely. Other new objects directly return the object because they do not get the lock
  once.Do(func() {
    config = new(Config)
    config.id = 1
    fmt.Println("new config")
  })
  fmt.Println("get config")
  return config
}

func main() {
  for i := 0; i < 3; i++ {
    _ = getConfig()
  }
}

result

new config
get config
get config
get config

Source code implementation

Interviewer: then you know sync What is the underlying structure of once?

Test site: sync Once source code

Super: sync Once is implemented by the once structure and Do and doSlow methods

type Once struct {
  // done indicates whether the action has been performed.
  // It is first in the struct because it is used in the hot path.
  // The hot path is inlined at every call site.
  // Placing done first allows more compact instructions on some     architectures (amd64/x86),
  // and fewer instructions (to calculate offset) on other architectures.
  done uint32
  m    Mutex
}

Done is the identification bit, which is used to judge whether the method f has been executed. The initial value of done is 0. When the execution of F is completed, done is set to 1.

m performs race state control. When the first execution of f is not finished, m blocks other once by locking Do execute f.

Here's a place to pay special attention to, once Do cannot be nested. Nested use will lead to deadlock.

func (o *Once) Do(f func()) {
  // Note: Here is an incorrect implementation of Do:
  //
  //  if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
  //    f()
  //  }
  //
  // Do guarantees that when it returns, f has finished.
  // This implementation would not implement that guarantee:
  // given two simultaneous calls, the winner of the cas would
  // call f, and the second would return immediately, without
  // waiting for the first's call to f to complete.
  // This is why the slow path falls back to a mutex, and why
  // the atomic.StoreUint32 must be delayed until after f returns.

  if atomic.LoadUint32(&o.done) == 0 {
    // Outlined slow-path to allow inlining of the fast-path.
    o.doSlow(f)
  }
}

func (o *Once) doSlow(f func()) {
  o.m.Lock()
  defer o.m.Unlock()
  if o.done == 0 {
    defer atomic.StoreUint32(&o.done, 1)
    f()
  }
}
  • Do() method

Function: judge o.done through atomic operation. If o.done==0, f is not executed completely, enter doSlow(f func()), and exit Do() if f is executed completely.

Input parameter: None

Output parameter: None

  • doSlow(f func()) method

Function: execute f by locking, and set o.done to 1 at the end of F execution

Input parameter: execution body f, usually for object creation or module data loading

Output parameter: None

Interviewer: good, then you know atomic What is the function of compareandswapuint32 (& o.done, 0, 1)?

Test site: sync package understanding breadth

Super super: CompareAndSwapUint32 is referred to as CAS for short. Judging by atomic operation, when the value of o.done is equal to 0, make o.done equal to 1 and return true. When the value of o.done is not equal to 0, return false directly

Interviewer: can you say that atomic can be used in the Do() method Loaduint32 is directly replaced by atomic Compareandswapuint32?

Test site: multithreaded thinking

Super super: This is not allowed, because the execution of f takes time. If CAS is used, the object created by f may not be completed, and f can be called elsewhere in the program. As shown in the figure, both A and B coprocessors call once Do method: the A coroutine completes CAS first and sets the done value to 1, which causes the B coroutine to mistakenly think that the object creation is completed, and then call the object method to make an error.

Here, the judgment of o.done == 0 in doSlow also needs attention, because it may occur that both a and B processes have been judged by LoadUint32 and are true. If the second verification is not carried out, the object will be new twice

expand

Interviewer: it seems that you are interested in the source code sync The implementation of once is still familiar. Do you know lazy mode and hungry mode?

Test site: how to create a single case

Super super:

Hungry man mode: it refers to loading data when the program starts, which avoids data conflict and is thread safe, but it may cause a waste of memory. For example, when the program starts, the Config object is built and the configuration information is loaded, but if the Config object is not used globally, it will cause a waste of memory.

Lazy mode: it means that when the program needs a config object, it takes the initiative to load data. This can avoid the waste of memory. For example, when it needs to call the config object to obtain data, it needs to create a new config object, and then obtain the configuration related information through the object.

Interviewer: it seems that I have studied this. Let's look at this question. You see, there are pictures, folders, app s and other files in the wastebasket. If you abstract this into various types of data, what container will you use to store it?

Chao chao: let me think about this (: why should I tell him I use a mac 😭

To be continued~

Welcome to the official account of "Golang interview" to see the latest progress of the article. If you have any questions what you want to ask, you would like to add WeChat to the readers and discuss with us.

Keywords: Go

Added by thedon on Wed, 09 Feb 2022 16:19:36 +0200