惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
L
LangChain Blog
Y
Y Combinator Blog
Microsoft Security Blog
Microsoft Security Blog
宝玉的分享
宝玉的分享
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
Jina AI
Jina AI
D
Docker
B
Blog
Engineering at Meta
Engineering at Meta
Last Week in AI
Last Week in AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
G
Google Developers Blog
博客园 - Franky
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The GitHub Blog
The GitHub Blog
T
The Blog of Author Tim Ferriss
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42

鸟窝

鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 LLM 究竟是如何工作的? Go 实验特性详解
Go sync 包近两年发展综述
by smallnest · 2025-10-01 · via 鸟窝

Go 语言的 sync 包是其并发编程模型的基石,提供了实现同步和并发控制的关键原语。在过去的两年里(大约从 Go 1.19 到 1.25),sync 包及其子包 sync/atomic 经历了一系列重要的演进。这些变化不仅包括新功能的增加,还涉及性能优化、内部实现的重构以及开发者体验的显著提升。

本文基于 Go 语言官方仓库的 Git 提交历史,对 sync 包近两年的主要变化进行总结。

1. 新增 API 与功能增强

为了简化常见的并发模式,sync 包引入了一些备受期待的新 API。

sync.WaitGroup.Go

Go 1.25 引入了 WaitGroup.Go 方法,极大地简化了在 WaitGroup 中启动 goroutine 的代码。

旧模式:

1

2

3

4

5

wg.Add(1)

go func() {

defer wg.Done()

}()

新模式:

1

2

3

wg.Go(func() {

})

这个辅助方法不仅减少了样板代码,还通过内置的 defer wg.Done() 调用避免了忘记调用 Done() 的常见错误。

代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

package main

import (

"fmt"

"sync"

"time"

)

func main() {

var wg sync.WaitGroup

urls := []string{

"http://example.com",

"http://example.org",

"http://example.net",

}

for _, url := range urls {

wg.Go(func() {

fmt.Printf("Fetching %s\n", url)

time.Sleep(100 * time.Millisecond)

fmt.Printf("Fetched %s\n", url)

})

}

wg.Wait()

fmt.Println("All fetches completed.")

}

sync.Map.Clearsync.Map.Swap

sync.Map 也获得了一些实用的新方法:

  • Clear(): 用于一次性删除 Map 中的所有键值对,提供了一种高效清空 Map 的标准方式 (Go 1.23.0)。
  • Swap(): 原子性地交换一个键的新旧值 (Go 1.20)。
  • CompareAndSwap() / CompareAndDelete(): 提供了更精细的原子操作,允许用户基于旧值进行条件交换或删除 (Go 1.20)。

代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

package main

import (

"fmt"

"sync"

)

func main() {

var m sync.Map

m.Store("config", "v1")

m.Store("feature_flag", "off")

oldValue, loaded := m.Swap("config", "v2")

if loaded {

fmt.Printf("Swapped 'config'. Old value was: %s\n", oldValue)

}

currentValue, _ := m.Load("config")

fmt.Printf("Current value of 'config' is: %s\n", currentValue)

fmt.Println("\nClearing the map...")

m.Clear()

m.Range(func(key, value interface{}) bool {

fmt.Println("This should not be printed.")

return true

})

fmt.Println("Map is empty.")

}

sync.Once 系列函数的演进

Go 1.21.0 引入的 OnceFunc, OnceValue, OnceValues 在后续版本中得到了优化,例如减少了堆内存分配,使其在需要延迟初始化或缓存计算结果的场景下更高效。func

  • OnceFunc(f func()) func():将一个无参数无返回值的函数包装成只执行一次的函数。
  • func OnceValue[T any](f func() T) func() T: 将一个无参数但有单个返回值的函数包装成只执行一次的函数,后续调用返回缓存的结果。
  • func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2): 将一个无参数但有两个返回值的函数包装成只执行一次的函数,后续调用返回缓存的结果。

代码示例 (OnceValue)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

package main

import (

"fmt"

"sync"

)

func main() {

once := sync.OnceValue(func() int {

sum := 0

for i := 0; i < 1000; i++ {

sum += i

}

fmt.Println("Computed once:", sum)

return sum

})

done := make(chan bool)

for i := 0; i < 10; i++ {

go func() {

const want = 499500

got := once()

if got != want {

fmt.Println("want", want, "got", got)

}

done <- true

}()

}

for i := 0; i < 10; i++ {

<-done

}

}

2. 性能优化与内部实现重构

sync 包的性能至关重要。近期的更新在多个方面对其进行了优化。

sync.Map 的新实现

sync.Map 的内部实现经历了一次重大变更,引入了基于哈希分片三角树(HashTrieMap)的新设计 (Go 1.24)。但这类探索表明了社区在持续寻求提升 sync.Map 在不同并发场景下性能的努力。

sync.Mutex 内部重构

Mutex 在Go1.24.0增加了一个基于HashTrieMap的实现。在Go1.26.0中应该会移除旧的sync.Mutex实现, sync.Map将默认采用HashTrieMap的实现。

https://github.com/golang/go/issues/70683

sync.Once 的原子操作优化

Once.done 字段的实现从 atomic.Uint32 切换到atomic.Bool (Go 1.25)。这不仅提升了代码的可读性和类型安全性,也代表了用现代原子类型替代旧有模式的趋势。

3. 代码正确性与开发者体验

为帮助开发者编写更健壮的并发代码,sync 包在文档和静态分析方面做了大量改进。

noCopy 哨兵的引入

Mutex, RWMutex, WaitGroup, Cond, 和 Map 等核心类型都加入了 noCopy 字段。这是一个特殊的非导出字段,可以被 go vet 工具识别。当开发者无意中复制了这些包含内部状态的同步原语时,go vet 会发出警告,从而在编译前发现潜在的并发 bug。

代码示例 (错误用法):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

package main

import "sync"

type Counter struct {

sync.Mutex

count int

}

func (c Counter) Inc() {

c.Lock()

c.count++

c.Unlock()

}

func main() {

var c Counter

c.Inc()

}

文档的持续改进

大量的提交都致力于改进和澄清文档,包括:

  • 明确指出 RWMutex 的读锁和写锁不能相互升级或降级。
  • 详细描述了 MapRangeDelete 等方法在并发访问下的行为和内存模型保证。
  • Cond.Wait 的行为提供了更清晰的说明。
  • 在包文档中直接链接到 Go 内存模型,强调其重要性。

4. sync/atomic 的现代化

作为 sync 的底层支撑,sync/atomic 包在 Go 1.19 中引入了基于泛型的类型安全原子类型(如 atomic.Int64, atomic.Pointer[T], atomic.Bool 等)。近两年的变化主要体现在:

  • 鼓励使用新类型:在 sync 包内部,旧的函数式原子操作(如 atomic.LoadUint32)正逐渐被新的类型化方法(如 myAtomicBool.Load())所取代。
  • API 完善:增加了 And/Or 等新的原子位操作函数,并对文档进行了补充,以指导用户从旧 API 过渡到新 API。

总结

过去两年,Go 的 sync 包在保持 API 稳定的同时,向着更易用、更安全、更高性能的方向稳步发展。通过引入 WaitGroup.Go 等便捷的辅助函数,开发者可以编写更简洁的并发代码。noCopy 哨兵和持续完善的文档则提高了代码的健壮性。底层的性能优化和实现重构,确保了 Go 的并发原语能够适应不断增长的性能需求。这些变化共同巩固了 Go 作为一门现代并发语言的地位。