Files
rikako-note/Golang/Golang Document.md
2025-01-11 16:17:27 +08:00

872 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

- [Golang](#golang)
- [Get Started](#get-started)
- [Enable dependency tracking](#enable-dependency-tracking)
- [go mod init](#go-mod-init)
- [go mod tidy](#go-mod-tidy)
- [multi-module workspace](#multi-module-workspace)
- [go work init](#go-work-init)
- [go work use](#go-work-use)
- [Gin框架构建restful api](#gin框架构建restful-api)
- [向response中写入返回数据](#向response中写入返回数据)
- [解析request中的数据](#解析request中的数据)
- [将请求的endpoint注册到server中](#将请求的endpoint注册到server中)
- [golang generic](#golang-generic)
- [不使用泛型的代码编写](#不使用泛型的代码编写)
- [使用泛型的代码编写](#使用泛型的代码编写)
- [comparable](#comparable)
- [泛型方法调用](#泛型方法调用)
- [type constraint](#type-constraint)
- [Fuzzing](#fuzzing)
- [unit test编写](#unit-test编写)
- [fuzz test编写](#fuzz-test编写)
- [执行fuzz test](#执行fuzz-test)
- [sync.Pool](#syncpool)
- [sync.Pool使用示例](#syncpool使用示例)
- [Pool和垃圾回收](#pool和垃圾回收)
- [poolCleanup](#poolcleanup)
- [allPools \& oldPools](#allpools--oldpools)
- [Proc Pining](#proc-pining)
- [per-P](#per-p)
- [local \& localSize](#local--localsize)
- [PinSlow](#pinslow)
- [Pool Local](#pool-local)
- [pool的Put/Get](#pool的putget)
- [Put](#put)
- [Get](#get)
- [slow path](#slow-path)
- [Sync.once](#synconce)
- [use case](#use-case)
- [syntax](#syntax)
- [iota](#iota)
# Golang
## Get Started
### Enable dependency tracking
当代码对其他module中包含的package进行了import时在自己的module中来管理依赖。
自己的module通过`go.mod`文件来定义,`go.mod`文件中会track项目所需要的依赖。
#### go mod init
`go mod init <module-name>`命令会创建一个`go.mod`文件,其中`<module-name>`会是module path。
在实际开发中module name通常是source code被保存的repository location例如`uuid`module的module name为`github.com/google/uuid`
#### go mod tidy
`go mod tidy`命令会根据import添加缺失的module并且移除未使用的module。
## multi-module workspace
示例目录结构如下所示:
- workspace
- workspace/hello
- workspace/example/hello
### go work init
在本示例中为了创建多module的workspace可以执行`go work init ./hello`,其会创建`go.work`文件,并将`./hello`目录下的module包含到`go.work`文件中。
`go.work`内容如下:
```
go 1.18
use ./hello
```
### go work use
通过`go work use ./example/hello`命令,会将`./example/hello`中的module加入到`go.work`文件中。
`go.work`内容如下:
```
go 1.18
use (
./hello
./example/hello
)
```
`go work use [-r] [dir]`命令行为如下:
- 如果指定目录存在,会为`dir``go.work`文件中添加一条use指令
- 如果指定目录不存在,会删除`go.work`文件中关于目录的use指令
## Gin框架构建restful api
在构建resultful api时通常都会通过json格式来传递数据首先可定义业务实体
```go
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
```
### 向response中写入返回数据
可以通过调用`gin.Context``IndentedJSON`方法来向response中写入数据
```go
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
```
### 解析request中的数据
可以通过`gin.Context``BindJSON`方法来将请求体中的数据解析到对象中。
```go
// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
var newAlbum album
// Call BindJSON to bind the received JSON to
// newAlbum.
if err := c.BindJSON(&newAlbum); err != nil {
return
}
// Add the new album to the slice.
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
```
### 将请求的endpoint注册到server中
可以将各个请求的处理handler注册到server中并在指定端口上运行server
```go
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
```
上述示例中,分别向`/albums`路径注册了GET和POST的处理handler并在`localhost:8080`上对服务进行监听。
## golang generic
### 不使用泛型的代码编写
如果不使用泛型,那么对于不同数值类型的求和,需要编写多个版本的代码,示例如下:
```go
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
```
上述针对int64和float64的版本编写了两个独立的函数
### 使用泛型的代码编写
对于泛型方法的编写,其相对普通方法多了`类型参数`,在对泛型方法进行调用时,可以传递类型参数和普通参数。
对于每个`parameter type`,其都有对应的`type constraint`。每个`type constraint`都制定了在调用泛型方法时,可以对`parameter type`指定哪些类型。
`type parameter`通常都带代表一系列类型的集合但是在编译时type parameter则是代表由调用方传递的`type argument`类型。如果type argument类型不满足type constraint的要求那么该代码则不会被成功编译。
type parameter必须要支持`generic code`中执行的所有操作。
```go
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
```
#### comparable
在上述示例中,`K`的constraint为`comparable`go中预先声明了该constraint。comparable约束代表其可以类型可以通过`==``!=`符号来进行比较。
golang中要求key类型为comparable。
上述示例中V的类型约束为`float64 | int64`,`|`符号代表取两种类型的并集,实际类型可以为两种类型中的任何一种。
### 泛型方法调用
```go
// 指定类型参数
fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))
// 不指定类型参数
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
SumIntsOrFloats(ints),
SumIntsOrFloats(floats))
```
在调用golang的泛型方法时可以省略类型参数此时编译器会推断类型参数类型但是对于部分场景例如没有参数只有返回值的参数`func returnObj[V any]() V`,此时泛型类型无法被推断,只能手动指定。
### type constraint
在golang中支持将泛型约束声明为接口并在多个地方重用该接口。通过将约束声明为接口可以避免复杂泛型约束的重复声明。
可以将泛型constraint声明为接口并且允许任何类型实现该接口将接口用作type constraint的指定那么传递给方法的任何类型参数都要实现该接口包含该接口中所有的方法。
代码示例如下:
```go
type Number interface {
int64 | float64
}
// SumNumbers sums the values of map m. It supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
fmt.Printf("Generic Sums with Constraint: %v and %v\n",
SumNumbers(ints),
SumNumbers(floats))
```
## Fuzzing
如下为一个反转字符串的示例:
```go
package fuzz
func Reverse(str string) (ret string, err error) {
bytes := []byte(str)
for i, j := 0, len(bytes)-1;i<j; i, j = i+1, j-1 {
bytes[i], bytes[j] = bytes[j], bytes[i]
}
ret = string(bytes)
return
}
```
### unit test编写
如果要对上述代码进行单元测试,可以手动在代码中指定测试用例,并对返回值进行断言判断。
如果想要针对golang文件创建unit test可以按照如下步骤
- 为go文件创建对应的`_test.go`文件,例如`reverse.go`文件,其对应单元测试文件为`reverse_test.go`
- 为go文件中想要测试的方法`_test.go`文件中创建对应的`TestXxxx`方法,例如对`Reverse`方法,可以创建名为`TestReverse`的测试方法
在完成上述步骤并且完成测试方法逻辑的编写后,可以调用`go test`命令执行单元测试。
示例如下所示:
```go
package main
import (
"testing"
)
func TestReverse(t *testing.T) {
testcases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{" ", " "},
{"!12345", "54321!"},
}
for _, tc := range testcases {
rev := Reverse(tc.in)
if rev != tc.want {
t.Errorf("Reverse: %q, want %q", rev, tc.want)
}
}
}
```
### fuzz test编写
在对功能进行测试时相对于手动编码测试用例fuzz能够自动生成用例并可能触及到手动用例无法触及的边界用例。
但是相较于unit textfuzz无法预知传递的测试参数也无法预测参数所对应的结果。
> 在编写fuzz test时可以将unit test, fuzz test, benchmark都包含在同一个`_test.go`文件中
在编写fuzz test方法时步骤如下
- fuzz test方法以`FuzzXxx`开头,和`TestXxx`类似
- 当调用`f.Add`会将参数作为seed添加
Fuzz test示例如下
```go
func FuzzReverse(f *testing.F) {
testcases := []string{"Hello, world", " ", "!12345"}
for _, tc := range testcases {
f.Add(tc) // Use f.Add to provide a seed corpus
}
f.Fuzz(func(t *testing.T, orig string) {
rev := Reverse(orig)
doubleRev := Reverse(rev)
if orig != doubleRev {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
}
```
#### 执行fuzz test
在编写完上述代码后,首先应该执行`go test`命令确保作为seed被添加的cases能够通过测试
```bash
go test
```
> 如果,只想执行指定的测试方法,可以指定`-run`参数,示例如下
>
> ```bash
> go test -run=FuzzReverse
> ```
`go test`执行通过之后,应该执行`go test -fuzz=Fuzz`命令,其会将随机产生的输入作为测试。
如果存在随机生成的用例测试不通过会将该用例写入到seed corups文件中`在下次go test命令被执行时即使没有指定-fuzz选项被写入文件中的用例也会被在此执行`
> 当通过`go test -fuzz=Fuzz`执行fuzz test时测试会一直进行直到被`Ctrl + C`中断。
>
> 如果要指定fuzz test的时间可以指定`-fuzztime`选项。
>
> 示例如下:
> ```bash
> go test -fuzz=Fuzz -fuzztime 30s
> ```
fuzz test的输出结果示例如下
```powershell
PS D:\CodeSpace\demo\fuzz> go test -fuzz=Fuzz -fuzztime 10s
fuzz: elapsed: 0s, gathering baseline coverage: 0/56 completed
fuzz: elapsed: 0s, gathering baseline coverage: 56/56 completed, now fuzzing with 32 workers
fuzz: elapsed: 3s, execs: 3028153 (1008398/sec), new interesting: 2 (total: 58)
fuzz: elapsed: 6s, execs: 6197524 (1057429/sec), new interesting: 2 (total: 58)
fuzz: elapsed: 9s, execs: 9423882 (1075420/sec), new interesting: 2 (total: 58)
fuzz: elapsed: 10s, execs: 10482150 (926639/sec), new interesting: 2 (total: 58)
PASS
ok git.kazusa.red/asahi/fuzz-demo 10.360s
```
> #### new interesting
> `new interesting`指会扩充code coverage的用例输入在fuzz test刚开始时new interesting数量通常会因发现新的代码路径快速增加然后会随着时间的推移逐渐减少
## sync.Pool
`sync.Pool`为golang标准库中的实现用于降低allocation和减少垃圾回收。
### sync.Pool使用示例
golang中`sync.Pool`使用示例如下:
```go
package main
import (
"fmt"
"sync"
)
type JobState int
const (
JobStateFresh JobState = iota
JobStateRunning
JobStateRecycled
)
type Job struct {
state JobState
}
func (j *Job) Run() {
switch j.state {
case JobStateRecycled:
fmt.Println("this job came from the pool")
case JobStateFresh:
fmt.Println("this job just got allocated")
}
j.state = JobStateRunning
}
func main() {
pool := &sync.Pool{
New: func() any {
return &Job{state: JobStateFresh}
},
}
// get a job from the pool
job := pool.Get().(*Job)
// run it
job.Run()
// put it back in the pool
job.state = JobStateRecycled
pool.Put(job)
}
```
### Pool和垃圾回收
`sync.Pool`对象实际是由两部分组成:
- `local pool`
- `victim pool`
调用Pool中的方法时实际行为如下
- `Put`调用Put方法时会将对象添加到`local`
- `Get`调用Get时首先会从`local`中查找,如果`local`中未找到,那么会从`victim`中查找如果victim中仍然不存在那么则是会调用`New`
`sync.Pool`中,`local`被用作primary cachevictim则被用作victim cache。
#### poolCleanup
poolCleanUp方法实现如下
```go
func poolCleanup() {
// This function is called with the world stopped, at the beginning of a garbage collection.
// It must not allocate and probably should not call any runtime functions.
// Because the world is stopped, no pool user can be in a
// pinned section (in effect, this has all Ps pinned).
// Drop victim caches from all pools.
for _, p := range oldPools {
p.victim = nil
p.victimSize = 0
}
// Move primary cache to victim cache.
for _, p := range allPools {
p.victim = p.local
p.victimSize = p.localSize
p.local = nil
p.localSize = 0
}
// The pools with non-empty primary caches now have non-empty
// victim caches and no pools have primary caches.
oldPools, allPools = allPools, nil
}
var (
allPoolsMu Mutex
// allPools is the set of pools that have non-empty primary
// caches. Protected by either 1) allPoolsMu and pinning or 2)
// STW.
allPools []*Pool
// oldPools is the set of pools that may have non-empty victim
// caches. Protected by STW.
oldPools []*Pool
)
func init() {
runtime_registerPoolCleanup(poolCleanup)
}
```
#### allPools & oldPools
所有被实例化的`sync.Pool`对象,`在修生变化时`,都会将其自身注册到`allPools`静态变量中。
其中,`allPools`引用了所有`local`(primary cache)不为空的pool实例`oldPools`则引用了所有`victim`(victim cache)不为空的pool实例。
在init方法中将poolCleanup注册到了runtime在STW的上线文中poolCleanup将会`在垃圾回收之前`被runtime调用。
poolCleanup方法逻辑比较简单具体如下
-`victim`丢弃,并且将`local`转移到`victim`,最后将`local`置为空
- 将静态变量中`allPools`的值转移到`oldPools`,并且将`oldPools`的值置为空
这代表如果pool中的对象如果长期未被访问那么将会从pool中被淘汰。
> `poolCleanUp`方法在STW时会被调用第一次STW时未使用对象会从local移动到victim而第二次STW则是会从victim中被丢弃之后被后续的垃圾回收清理。
### Proc Pining
关于`sync.Pool`,其实际结构如下:
```go
type Pool struct {
noCopy noCopy
local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
localSize uintptr // size of the local array
victim unsafe.Pointer // local from previous cycle
victimSize uintptr // size of victims array
// New optionally specifies a function to generate
// a value when Get would otherwise return nil.
// It may not be changed concurrently with calls to Get.
New func() any
}
```
#### per-P
关于调度的actor其存在如下角色
- goroutine`G's`
- machines`M's`代表系统线程
- processor`P's`代表处理器物理线程
其中,`goroutine`由操作系统线程执行而操作系统线程在执行时需要获取实际的cpu物理线程。
在gouroutine运行时存在一些`safe-point`,在`safe-point`goroutine可以在`clean`状态被停止。故而,`抢占只能发生在safe-point`
`proc pinning`会禁止抢占在pinning后P物理线程将会被独占`unpin`发生之前goroutine会一直执行并不会被停止甚至不会被GC停止。`unpin之前P无法被其他goroutine使用`
一旦`pinned`execution flow在P上不会被中断`这也意味着在访问threadlocal数据时无需加锁`
如下是围绕Pinning的逻辑
```go
// pin pins the current goroutine to P, disables preemption and
// returns poolLocal pool for the P and the P's id.
// Caller must call runtime_procUnpin() when done with the pool.
func (p *Pool) pin() (*poolLocal, int) {
pid := runtime_procPin()
// In pinSlow we store to local and then to localSize, here we load in opposite order.
// Since we've disabled preemption, GC cannot happen in between.
// Thus here we must observe local at least as large localSize.
// We can observe a newer/larger local, it is fine (we must observe its zero-initialized-ness).
s := runtime_LoadAcquintptr(&p.localSize) // load-acquire
l := p.local // load-consume
if uintptr(pid) < s {
return indexLocal(l, pid), pid
}
return p.pinSlow()
}
func indexLocal(l unsafe.Pointer, i int) *poolLocal {
lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
return (*poolLocal)(lp)
}
```
#### local & localSize
- `local`local是一个由`poolLocal`对象组成`c-style`数组
- `localSize`localSize是`local`数组的大小
- `poolLocal`: local数组中的每个poolLocal都关联一个给定的P
- `runtime_procPin`:该方法会返回`pin`锁关联的processor idprocessor id从0开始依次加1直到`GOMAXPROCS`
分析上述`indexLocal`方法的逻辑其根据processor id的值计算了pid关联poolLocal对象地址的偏移量并返回poolLocal对象的指针。这令我们可以并发安全的访问poolLocal对象而无需加锁`只需要pinned并且直接访问threadlocal变量`
#### PinSlow
`pinSlow`方法是针对`pin`的fallback方法其代表我们针对local数组大小的假设是错误的本次绑定的P其并没有对应的poolLocal。
代码进入到pinSlow有如下可能
- `GOMAXPROCS`被更新过从而有了额外可用的P
- 该pool对象是新创建的
pinSlow的代码如下
```go
func (p *Pool) pinSlow() (*poolLocal, int) {
// Retry under the mutex.
// Can not lock the mutex while pinned.
runtime_procUnpin()
allPoolsMu.Lock()
defer allPoolsMu.Unlock()
pid := runtime_procPin()
// poolCleanup won't be called while we are pinned.
s := p.localSize
l := p.local
if uintptr(pid) < s {
return indexLocal(l, pid), pid
}
if p.local == nil {
allPools = append(allPools, p)
}
// If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one.
size := runtime.GOMAXPROCS(0)
local := make([]poolLocal, size)
atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
runtime_StoreReluintptr(&p.localSize, uintptr(size)) // store-release
return &local[pid], pid
}
```
当处于`pinned`状态时,无法获取针对`allPools`变量的锁,这样有可能会导致死锁。
> 如果在处于pinned状态的情况下获取锁那么此时锁可能被其他goroutine持有而持有锁的goroutine可能正在等待我们释放P
故而在pinSlow中首先`unpin`,然后获取锁,并且在获取锁之后重新进入`pin`状态
在重新进入pin状态并且获取到allPoolsMu的锁之后首先会检测目前pid是否有关联的poolLocal对象如果有则直接返回这通常在如下场景下发生:
- 在阻塞获取allPoolsMu锁时其他goroutinue已经为我们扩充了local数组的大小
- 我们不再绑定在之前的P上了我们可能绑定在另一个pid小于local数组大小的P上
如果目前pool对象其local数组为空那么其会先将pool实例注册到allPools中然后执行如下逻辑
- 创建一个新的poolLocal sliceslice大小和GOMAXPROCS相同并将新创建slice的头一个元素地址存储到`p.local`
- 将slice大小存储在`p.localSize`
### Pool Local
`poolLocal`结构如下:
```go
// Local per-P Pool appendix.
type poolLocalInternal struct {
private any // Can be used only by the respective P.
shared poolChain // Local P can pushHead/popHead; any P can popTail.
}
type poolLocal struct {
poolLocalInternal
// Prevents false sharing on widespread platforms with
// 128 mod (cache line size) = 0 .
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
```
> 对于poolLocalIntenral中的poolChainlocal P可以执行pushHead/popHead逻辑而任何P都可以执行popTail逻辑
### pool的Put/Get
#### Put
其中Put相关逻辑如下
```go
// Put adds x to the pool.
func (p *Pool) Put(x any) {
if x == nil {
return
}
if race.Enabled {
if fastrandn(4) == 0 {
// Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
l, _ := p.pin()
if l.private == nil {
l.private = x
} else {
l.shared.pushHead(x)
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
}
}
```
其核心逻辑如下:
- pin并获取poolLocal
- 如果poolLocal中private为空将item放到private中
- 如果private不为空将其放入shared中LIFO
- 然后unpin
#### Get
Get的相关逻辑如下
```go
// Get selects an arbitrary item from the Pool, removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to Put and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
func (p *Pool) Get() any {
if race.Enabled {
race.Disable()
}
l, pid := p.pin()
x := l.private
l.private = nil
if x == nil {
// Try to pop the head of the local shard. We prefer
// the head over the tail for temporal locality of
// reuse.
x, _ = l.shared.popHead()
if x == nil {
x = p.getSlow(pid)
}
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
if x == nil && p.New != nil {
x = p.New()
}
return x
}
```
以下是pool的Get核心流程
- pin, 并且获取poolLocal
- 将private清空并且判断之前private是否有值如果有值将使用该值
- 如果private之前没有值那么对shared执行pop操作LIFO如果pop操作获取的值不为空使用该值
- 如果对shared执行LIFO pop操作的也为空那么会执行slow path的getSlow方法
- 如果在getSlow仍然未获取到值的情况下会调用`New`方法来获取值
> #### LIFO
> 对于poolLocal的shared队列其使用的是LIFO最后添加到队列的元素会被最先弹出。这代表我们希望使用最新分配的对象旧分配的对象会随着`STW`被逐渐淘汰。
##### slow path
在调用Get方法时slow path仅当private和shared都为空时被触发这代表当前threadlocal pool为空。
在触发slow path场景下会尝试从其他P中窃取对象如果在窃取仍然失败的场景下才会去`victim`中进行查找。
Get方法中slow path实现如下
```go
func (p *Pool) getSlow(pid int) any {
// See the comment in pin regarding ordering of the loads.
size := runtime_LoadAcquintptr(&p.localSize) // load-acquire
locals := p.local // load-consume
// Try to steal one element from other procs.
for i := 0; i < int(size); i++ {
l := indexLocal(locals, (pid+i+1)%int(size))
if x, _ := l.shared.popTail(); x != nil {
return x
}
}
// Try the victim cache. We do this after attempting to steal
// from all primary caches because we want objects in the
// victim cache to age out if at all possible.
size = atomic.LoadUintptr(&p.victimSize)
if uintptr(pid) >= size {
return nil
}
locals = p.victim
l := indexLocal(locals, pid)
if x := l.private; x != nil {
l.private = nil
return x
}
for i := 0; i < int(size); i++ {
l := indexLocal(locals, (pid+i)%int(size))
if x, _ := l.shared.popTail(); x != nil {
return x
}
}
// Mark the victim cache as empty for future gets don't bother
// with it.
atomic.StoreUintptr(&p.victimSize, 0)
return nil
}
```
- 首先,会尝试对`pool.local`数组中所有的poolLocal对象都调用popTail方法如果任一方法返回值不为空那么将会使用该返回的值。`窃取操作会尝试窃取尾部的对象,这是最先被创建的对象`
- 如果在local中未能找到和窃取到对象那么会从victim中进行查找
- 首先获取victim中当前pid对象的poolLocal对象检查poolLocal对象private是否不为空如果不为空使用该值并将victim.private清空
- 如果private为空那么则对victim中所有P关联的poolLocal对象执行popTail操作如果任何一个pop操作返回不为空那么使用返回的对象
- 如果所有victim中的poolLocal对象都返回为空那么会将victim中`p.victimSize`标识为空后续再次执行slow path时如果感知到victimSize为空那么便不会再次查找victim
## Sync.once
sync.Once是golang中提供的工具包确保指定的代码块在并发环境下只会被执行一次。
sync.Once为struct其中只包含一个方法`Do(f func())`。sync.Once保证指定方法只会被执行一次即使在多routine环境下被调用了多次。`并且sync.Once方法是线程安全的`
### use case
sync.Once的用例如下
- 对共享的资源进行初始化
- 设置单例
- 执行只需要运行一次的高开销计算
- 导入配置文件
sync.Once的使用示例如下
```go
var instance *singleton
var once sync.Once
func getInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
```
sync.Once实现逻辑如下
```go
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/386),
// and fewer instructions (to calculate offset) on other architectures.
done atomic.Uint32
m Mutex
}
func (o *Once) Do(f func()) {
if o.done.Load() == 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.Load() == 0 {
defer o.done.Store(1)
f()
}
}
```
## syntax
### iota
`iota`关键字代表连续的整数变量,`0, 1, 2`,每当`const`关键字出现时其重置为0
其使用示例如下
```go
package main
import "fmt"
const (
ZERO = iota
ONE
)
const TWO = iota
const (
THREE = iota
FOUR
)
func main() {
fmt.Println(ZERO) // 0
fmt.Println(ONE) // 1
fmt.Println(TWO) // 0
fmt.Println(THREE) // 0
fmt.Println(FOUR) // 1
}
```
另外const关键字也支持如下语法
```go
package main
import (
"fmt"
"reflect"
)
type Shiro uint8
const (
ZERO Shiro = iota
ONE
)
const TWO Shiro = iota
const (
THREE Shiro = iota
FOUR
)
func main() {
fmt.Printf("ZERO %d, %s\n", ZERO, reflect.TypeOf(ZERO).Name())
fmt.Printf("ONE %d, %s\n", ONE, reflect.TypeOf(ONE).Name())
fmt.Printf("TWO %d, %s\n", TWO, reflect.TypeOf(TWO).Name())
fmt.Printf("THREE %d, %s\n", THREE, reflect.TypeOf(THREE).Name())
fmt.Printf("FOUR %d, %s\n", FOUR, reflect.TypeOf(FOUR).Name())
}
```
当const在`()`中声明多个常量时,如首个常量的类型被指定,则后续常量类型可省略,后续类型与首个被指定的类型保持一致。