Go sync 包近两年发展综述

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()
// ... do work ...
}()

新模式:

1
2
3
wg.Go(func() {
// ... do work ...
})

这个辅助方法不仅减少了样板代码,还通过内置的 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
31
32
33
package main

import (
"fmt"
"sync"
"time"
)

func main() {
var wg sync.WaitGroup
urls := []string{
"http://example.com",
"http://example.org",

<!--more-->

"http://example.net",
}

for _, url := range urls {
// 使用 wg.Go 启动 goroutine
wg.Go(func() {
// 模拟抓取 URL
fmt.Printf("Fetching %s\n", url)
time.Sleep(100 * time.Millisecond)
fmt.Printf("Fetched %s\n", url)
})
}

// 等待所有 wg.Go 启动的 goroutine 完成
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")

// Swap: 原子性地将 "config" 的值更新为 "v2",并返回旧值
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)

// Clear: 清空整个 Map
fmt.Println("\nClearing the map...")
m.Clear()

// 验证 Map 是否为空
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"

// Counter 是一个带有锁的计数器
type Counter struct {
sync.Mutex
count int
}

// Inc 增加计数器
func (c Counter) Inc() { // 注意:这里使用了值接收器,会导致 Mutex 复制
c.Lock()
c.count++
c.Unlock()
}

func main() {
var c Counter
c.Inc()
}

// 运行 `go vet .` 将会报告:
// main.go:12:6: call of method Inc copies lock value: main.Counter contains sync.Mutex

文档的持续改进

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

  • 明确指出 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 作为一门现代并发语言的地位。

使用Linux 30年了,我都不知道 ping 8.8 还能这么用?

今天看到 @sysxplore 介绍的一个技巧,类似IPv6的0简写的方法,这是我第一次知道ipv4的地址还能这么直接写:
![](/127.1/admin # 可能绕过 "127.0.0.1" 的黑名单
http://2130706433/ # 十进制形式的 127.0.0.1


### 2. 日志分析困难

不同格式的相同IP可能导致日志分析和安全审计的复杂化。

### 3. 防火墙规则绕过

某些防火墙规则可能无法识别非标准格式的IP地址。

## 兼容性说明

这个特性的支持情况因系统和应用而异:

- ✅ **完全支持:** Linux、macOS、BSD系统的网络工具
- ⚠️ **部分支持:** Windows系统(某些版本的 `ping` 支持,但浏览器通常不支持)

<!--more-->

- ❌ **不支持:** 大多数现代Web浏览器(出于安全考虑)、某些编程语言的标准库

## 建议

知道就好,就像知道回字有几种写法,现在几乎不会这么使用。


**参考资料:**

- BSD Socket API Documentation
- inet_aton() Manual Pages
- IETF RFC 3986 (URI Generic Syntax)

从 AI 哪里挣钱?

来自投资人 @JTLonsdale 的见解。


根据国际能源署,数据中心电力消耗预计到 2030 年将增加一倍以上,达到约 945 太瓦时。 以这个数据为背景,这超过了大多数国家。 例如,德国在 2024 年大约产生了 431.7 太瓦时的电力。

Tier 1 一级芯片层。

由于通用 CPU 无法处理 AI 的需求,您需要为所有这些并行处理提供完全不同的硅架构。 随着 AI 模型复杂性的增加,为其提供动力的芯片也必须保持同步。

一些关键的近期芯片“军备竞赛”统计数据: - Groq 在 69 亿美元估值下融资 7.5 亿美元,以挑战 NVIDIA。 - 台积电将其美国投资扩大至 1650 亿美元。 2024 年,整个 AI 芯片市场达到 1230 亿美元,预计到 2030 年将以 33% 的年增长率增长。

Tier 3 覆盖基础模型公司

这些公司需要大量的前期资本进行训练,但它们创造了推动堆栈中所有上层功能的能力。 例如 OpenAI、Anthropic 和谷歌的 Gemini。

这些公司层次似乎是一个明显的投资选择,因为它们总是成为头条新闻。 OpenAI 的估值达到了 3000 亿美元,收入从 37 亿美元增长到 127 亿美元,年化增长仅几个月。Anthropic 的最新估值也增长了 3 倍,达到 1830 亿美元。 这里的限制是,这些公司的训练成本呈指数级增长。GPT-4 的训练成本为 1 亿美元,未来的模型将超过 10 亿美元。

Tier 4 软件基础设施

把这想成是 AI 的镐子和铲子。 没有部署平台、检索向量数据库和用于管理整个流程的 MLOps 工具,模型将无法存在。

Databricks 的估值达到 1000 多亿美元,推动企业 AI 工作流程。 就在去年,矢量数据库初创公司筹集了 2 亿美元(Vespa、Weaviate、Pinecone 和 Chroma)以支持 LLM 基础设施。langchain完成了1.25亿美元的融资,估值已经达到了12.5亿美元。

阅读全文

深入了解 Go 1.25 的新特性:Trace Flight Recorder

Go 1.25 引入了一个令人兴奋的新特性:Trace Flight Recorder (飞行记录)。这个工具为 Go 开发者提供了一种更高效、更轻量级的生产环境调试和性能分析方法。本文将深入探讨 Trace Flight Recorder 的工作原理、配置方式、使用示例以及它在实际应用中的价值。

参考这张flightrecorder的图片,生成一个更加现代、彩色、有趣的图片

什么是 Tracing 和 Flight Recording?

在深入了解 Trace Flight Recorder 之前,我们首先需要理解两个核心概念:

  • Tracing (跟踪): Tracing 是一种监控和调试技术,通过收集程序执行的详细信息,例如函数调用、goroutine 活动、内存分配等,来帮助开发者识别性能瓶颈和调试复杂问题。传统的 tracing 方式通常会记录程序的整个生命周期,这可能会导致生成巨大的跟踪文件,带来较高的开销。

阅读全文

Go 语言全新实验性 JSON API

前言

JavaScript 对象表示法(JSON) 是一种简洁的数据交换格式。大约 15 年前,我们曾经介绍过 Go 语言对 JSON 的支持,当时引入了将 Go 类型序列化为 JSON 数据以及从 JSON 数据反序列化的功能。从那时起,JSON 已经成为互联网上最受欢迎的数据格式。Go 程序广泛地读写 JSON 数据,encoding/json 现在是 Go 语言第 5 大最常用的导入包。

随着时间推移,软件包会根据用户需求不断演进,encoding/json 也不例外。本文将介绍 Go 1.25 中全新的实验性包 encoding/json/v2encoding/json/jsontext,它们带来了期待已久的改进和修复。

本文将论证为什么需要一个全新的主要 API 版本,并概述这些新包的特性,解释如何使用它们。这些实验性包默认情况下不可见,未来的 API 可能会发生变化。

encoding/json 存在的问题

总体而言,encoding/json 运行良好。将任意 Go 类型编组(marshaling)和解组(unmarshaling)为 JSON 的默认表示方式,结合自定义表示的能力,已经证明了其高度的灵活性。然而,自推出以来的这些年里,各种用户发现了许多不足之处。

阅读全文

Go 语言的内存分配机制

Go 语言的内存分配机制

目录

免责声明

这篇博文主要关注在 ARM 架构 Linux 系统上运行的 Go 1.24 编程语言。内容可能不涵盖其他操作系统或硬件架构的平台特定细节。

本文基于其他资料和我对 Go 的理解,可能并非完全准确。如有错误或建议,欢迎在最底部的评论区指正 😄。

引言

内存分配是每个编程语言运行时的核心,Go 也不例外。内存的高效分配和管理直接影响 Go 应用程序的性能、可扩展性和响应能力。

虽然 Go 通过简单的 API(new(T)&T{}make)抽象了大部分复杂性,但理解底层原理能让我们深入了解运行时如何实现高效性,以及潜在的性能瓶颈可能出现在哪里。

阅读全文

老师,谢谢您

我的家乡是鲁西南的一个小县城下面的一个小村子,人口也就几百人,几乎都是一个姓氏。村子不大,但是向来崇文,因为我高祖父兄弟二人同中秀才,在附近县城小有名气,村子也被成为秀才村。从小长辈们常常说起他们的事情,引以为傲。村子虽不大,却有一个小学,只教一到三年级,整个学校也就两三个教师,教室是土房子,搭着木板当课桌,这是八十年代初,虽然条件简陋,但是当时的老师兼校长还是引入了很多现在看起来很先进的教学理念,有音乐课,教我们唱歌;有体育课,教我们打球;鼓励式教学,老师叫晁代芬,也是本村的知识分子。四年级我们就得到邻村的一个大一点的学校去上学了。再后来村里新建了红瓦房,房屋条件是村里最好的了。再后来农村撤校,学校就停办了。

初中在邻村上的,也是80年代农村常见的红瓦平方,有几排。初三的时候有个乡里的老师调来当校长,很有能力,对老师和学生管的非常严厉。当时的班主任兼数学老师是一位年纪很大的老师了,但是教学也是很厉害的,他教过我的父亲,我的姐姐和我。别的同学都说他很严厉,但是我没觉得,反而觉得他很好。哪一年难得这个农村中学有三个考上了县上最好的中学,包括我姐姐、我和另外一个同学。

高中是县里的一中,也是县里最好的中学了。那是那几年高考成绩一般,能有考上山东大学的就不错了。我入学那年,年级主任是乔老师,据说是从二中调过来的,后来我毕业后升为了校长。他是一个实干派,想做出成绩出来,对老师抓的特别近。记得初二的时候让学生给老师写评价,有几个老师的评价比较低,乔校长应该狠狠批了一通,有的老师在课堂上哭了,说你们这群白眼狼。还有那个时候管后勤的不知道抽什么疯,傍晚下课后就把教室的电断掉,省钱,气的乔校长就和管后勤的老师吵起来,让恢复供电,让喜欢学习的学生可以在教室了写作业。班主任孟老师也非常好,对大家的学习也盯得紧。那一年高考有四个同学考上了北大、三个中科大、还有西安交大、哈工大等,那是那几年最辉煌的一届了。

阅读全文

容器感知 GOMAXPROCS

Go 1.25 带来了全新的容器感知 GOMAXPROCS 默认设置。这个改进让容器工作负载的默认行为变得更加合理,避免了影响尾延迟的限流问题,并提升了 Go 的开箱即用生产就绪性。

在这篇文章中,我们将深入探讨 Go 如何调度 goroutine,这种调度如何与容器级别的 CPU 控制交互,以及 Go 如何通过感知容器 CPU 控制来获得更好的性能。

GOMAXPROCS

Go 的一大优势是通过 goroutine 提供了内置且易用的并发能力。从语义角度来看,goroutine 与操作系统线程非常相似,让我们能够编写简单的阻塞代码。另一方面,goroutine 比操作系统线程更轻量,创建和销毁的成本要低得多。

阅读全文

我苦学得来的 20 个 Go 性能技巧

作为一名多年使用 Go 语言构建后端服务的工程师,我深刻意识到该语言的巨大性能潜力。但这种潜力需要被正确地解锁。在高并发环境下,仅仅实现一个功能和构建一个稳定高效运行的系统之间存在着巨大的差异。不良的编码习惯和忽视底层机制很容易抵消 Go 语言本身提供的性能优势。

这篇文章不是一堆抽象的理论。我将分享 20 条在生产环境中反复验证过的性能优化技巧。这些技巧是多年开发、调优和犯错后总结出的有效实践。我将深入探讨每条建议背后的“为什么”,并提供实用的代码示例,旨在构建一个清晰、可操作的 Go 性能优化框架。

优化的哲学:先原则后实践

在修改一行代码之前,你必须确立正确的优化方法。否则,你所有的努力都可能是徒劳的。

1. 优化的第一条规则:测量,不要猜测

为什么:任何没有数据支持的优化都是工程上的大忌——这就像在黑暗中摸索。工程师对瓶颈的直觉往往是不可靠的。“错误方向”的优化不仅浪费时间,还会引入不必要的复杂性,甚至可能引入新的错误。Go 内置的 pprof 工具集是我们最强大的武器,也是性能分析的唯一可靠起点。

  如何操作:

使用 net/http/pprof 包,您可以在 HTTP 服务中以最小的努力暴露一个 pprof 端点,实时分析其运行时状态。

  • CPU 剖析:定位消耗最多 CPU 时间的代码路径(热点)。
  • 内存剖析:分析程序的内存分配和保留情况,帮助查找不合理的内存使用。
  • 阻塞剖析:追踪导致 goroutine 阻塞的同步原语(锁、通道等待)。

阅读全文

Go 1.25 概览

Go 1.25 概览

Go 1.25标志着 Go 语言向前迈出了重要一步,其核心在于提升性能、优化开发者体验以及增强云原生就绪能力。此次发布在工具链、运行时、编译器和标准库等多个方面引入了一系列增强功能,旨在使 Go 应用程序更快、更高效,并更易于开发和部署,尤其是在容器化环境中。它还凸显了对安全性的承诺以及语言规范的持续完善。

本次发布围绕以下关键主题展开:性能优化、增强型开发者工具、云原生就绪、安全强化以及语言成熟度。以下表格概述了 Go 1.25的主要亮点,为繁忙的专业人士提供了快速参考,以便立即了解最关键的变更。

类别 特性/变更 简要描述 影响/益处
性能优化 实验性垃圾回收器 (greenteagc) 标记和扫描小对象性能提升,预计减少0-40% GC 开销 降低运营成本,提升应用吞吐量和降低延迟

阅读全文