atomic 原子操作

C++ 的 std::atomic 在 Go 里是 sync/atomic 包:无锁计数器、无锁标志位、无锁状态机的最优解 一、简述 sync/atomic 提供对内存地址的原子操作:Add(原子加)、Load(原子读)、Store(原子写)、CompareAndSwap(比较并交换)、Swap(交换)。它们作用于 int32 / int64 / uint32 / uint64 / uintptr / unsafe.Pointer 以及 atomic.Value。 和 Mutex 相比,原子操作是免锁(lock-free)的:既不阻塞、也无上下文切换开销,在「高频计数器、状态标志、一次性初始化标志」这类场景下性能明显优于锁。代价是只能保护单变量,且不能声明「一段代码的整齐」。 核心要点:读——Load、写——Store、改——Add / CompareAndSwap。把「多步的读-改-写」用一个 CAS 原子完成,是理解 Go 原子操作的钥匙。C++ 里的 std::atomic<int> 心智模型几乎原样平移,差异只在 API 命名和哪些类型支持。 二、C++ 对照速查表 概念 C++ Go 关键差异 原子整数 std::atomic<int> n{0} var n atomic.Int64(或 var n int64 + atomic.AddInt64) Go 1.19+ 有类型化 atomic.Int64,更顺手 原子加 n.fetch_add(1) atomic.AddInt64(&n, 1) Go 返回新值;AddInt32 / AddUint64 等按类型 原子读 n.load() atomic.LoadInt64(&n) Go 读不写也有可能被优化异常,必须用 Load 原子写 n.store(42) atomic.StoreInt64(&n, 42) Go 生硬但显式;Load/Store 配对使用 比较并交换 n.compare_exchange_strong(expected, desired) atomic.CompareAndSwapInt64(&n, old, new) CAS 失败返回 false,循环重试是惯用法 原子交换 n.exchange(42) atomic.SwapInt64(&n, 42) 有返回:交换前旧值 原子布尔/指针 std::atomic<bool> / std::atomic<T*> atomic.Bool(1.19+)/ atomic.Pointer[T](1.19+) Go 类型化版本避免手写 unsafe.Pointer 任意值原子存取 std::mutex 包 struct atomic.Value(存储任意类型) Value 的 Load 要求「上次存过的类型」 内存顺序 memory_order_relaxed / seq_cst Go 不暴露 memory_order,原子操作只有一种语义 自 Go 1.19 起内存模型正式规定:原子访问之间顺序一致(sequentially consistent) 三、逐主题详解 3.1 计数器:AddInt64 / AddUint64 经典场景——无锁计数器,多 goroutine 并发累加。 ...

2026 年 10 月 13 日

channel 详解

C++ 里你得自己造「互斥锁 + 条件变量 + 队列」才能做到的线程通信,Go 用一个 channel 就把它变成了语言级原语 一、简述 channel(通道)是 Go 并发模型的核心,它的口号是: 「Do not communicate by sharing memory; instead, share memory by communicating.」 —— 不要通过共享内存来通信,而要通过通信来共享内存。 一个 channel 就是一个带类型的 FIFO 队列,goroutine 之间通过它发送 / 接收数据,同时天然完成了同步(谁等谁、什么时候等)与数据传递两件事。 作为 C++ 开发者,你过去写线程同步大概是这样:std::mutex 保护共享数据 + std::condition_variable 通知 + std::queue 存数据 + 各种 unique_lock。而 Go 把这三件套打包成了一个 channel:发送阻塞、接收阻塞、容量限制、关闭通知全部内建,而且它在语言层面保证了 channel 操作本身的线程安全。 本笔记覆盖:创建、无缓冲 / 有缓冲的阻塞语义、单向 channel、关闭与 range 消费、nil channel、死锁场景,以及「channel 当锁」的奇技淫巧。 二、C++ 对照速查表 概念 C++ Go 关键差异 消息队列 std::queue<T> + std::mutex + std::condition_variable ch := make(chan T) Go 一条语句搞定,且是语言级原语 无缓冲阻塞队列 手写「空/满都等条件变量」的队列 make(chan T)(默认无缓冲) 无缓冲 channel 天然同步:发=等收 有缓冲队列 std::queue + 信号量限制 make(chan T, 5) 缓冲即容量,满则发送阻塞 发送操作 queue.push(x); cv.notify_one(); ch <- x <- 方向右:发送 接收操作 queue.pop();(空时等 cv) x := <-ch <- 方向左:接收 队列空时接收 条件变量等待 + 谓词循环 阻塞直到有值 Go 不需要手写谓词循环 队列满时发送 信号量 / 容量检查 阻塞直到有空位 缓冲满才阻塞,缓冲空才阻塞 生命周期管理 队列对象手动析构、小心悬垂 close(ch),只能发送方关闭 关闭后 range 自动结束 多读多写 一把锁 + 一个队列满危险 内建线程安全,任意并发收发 Go 收发都允许并发,无需额外锁 非阻塞操作 try_lock / 检查队列非空 select + default(下篇讲) select 是非阻塞收发的标准姿势 三、逐主题详解 3.1 创建 channel:make package main import "fmt" func main() { // 无缓冲 channel:接收方和发送方必须同时准备好,相当于一次「握手」 ch1 := make(chan int) // 有缓冲 channel:容量为 5,缓冲不满时发送不阻塞 ch2 := make(chan string, 5) // 只声明不 make 的 channel 是 nil channel var ch3 chan float64 fmt.Println(ch3 == nil) // true(nil channel 不能收发,见 3.7) fmt.Println(ch1 == nil, ch2 == nil) // false, false _ = ch1 _ = ch2 } ⚠️ C++ 对照:make(chan int) 既有「分配」又有「初始化」两层含义,像 std::make_shared<T>()。注意这里返回的是 channel 本身,不是一个指针——但你如果 ch2 := make(chan int) 再 ch2 = ch2,传递的都是同一个底层通道的引用,多个 goroutine 共享它时不需要再加锁。 ...

2026 年 10 月 13 日

CLI 工具 flag 与 cobra

从 C++ 的 Boost.Program_options / CLI11 到 Go:标准库 flag 包入门,再上手 cobra 这个事实标准的命令行框架 一、简述 命令行工具(CLI)是 Go 最擅长的领域之一:编译出来是单一静态二进制,启动快、分发简单,天然适合写 git / docker 这类开发者工具。Go 在命令行的「外挂」上做了很多设计——os.Args 给你原始参数,标准库 flag 包给你简单的 -name value 解析,而 spf13/cobra(Kubernetes、Hugo、Docker CLI 大量使用)则提供子命令、flag 继承、自动帮助和 shell 补全的完整方案。 核心要点:C++ 里解析命令行要么手写循环扫 argv,要么拉 Boost.Program_options / CLI11;Go 的路径是「标准库 flag 够用即用,复杂命令行直接上 cobra」。cobra 的「命令树」思想对 C++ 开发者来说很像「用对象组合搭一棵命令路由树」。 二、C++ 对照速查表 概念 C++ Go 关键差异 入口参数 int main(int argc, char* argv[]) func main() + os.Args os.Args[0] 是程序名,Go 是切片可遍历 参数解析库 Boost.Program_options / CLI11 flag(标准库) flag 零依赖,但功能简单 子命令框架 自己搭 / CLI11 github.com/spf13/cobra cobra 是事实标准(k8s/gh/hugo 都用) 类型自动转换 boost::lexical_cast flag 绑定即转换 flag.Int 自动转,无需显式 cast 帮助文本 手动拼接打印 flag / cobra 自动生成 -h / --help 开箱即用 长短选项 -v / --verbose -v / --verbose 等价 flag 单双横线都认;布尔无需值 子命令树 无标准方案 cobra AddCommand 层层挂载 命令即对象,可嵌套 shell 补全 无标准方案 cobra completion 命令 生成 bash/zsh/fish 脚本 解析失败行为 抛异常 / 返回错误码 flag 默认 ExitOnError 可切换到 ContinueOnError 自己处理 三、逐主题详解 3.1 os.Args —— 原始参数切片 任何命令行的起点。和 C++ 的 argc/argv 相比,Go 把它做成了普通切片,不用数个数、不用指针算术: ...

2026 年 10 月 13 日

context 超时取消与传值

C++ 没有直接对应的东西——它最接近 std::jthread 的 stop_token:一个穿过调用链的「取消令牌 + 截止时间 + 键值袋」 一、简述 context.Context(标准库 context 包)是 Go 并发中传播取消信号、截止时间与请求级数据的载体。它贯穿整个标准库(HTTP 请求、数据库、RPC),是每一个「可能被取消的耗时代码」的标配第一参数。 它解决 C++ 里三个长期痛点: 传递取消信号:深调用链中,任何一层想取消整条链,怎么办?C++ 用 std::atomic<bool> 到处传 + 检查,或 std::jthread 的 stop_token。Go 里就是 ctx.Done() + 广播。 传递截止时间:每个函数都检查「现在是不是超时了」很烦。Go 里 context.WithDeadline 一路带过去,子任务自己查 ctx.Done()。 传递请求级数据:分布式的 trace id、用户信息等「横切数据」,Go 用 WithValue 携带,避免每个函数都加参。 核心要点:context.Context 只做三件事:取消(Cancel)、截止(Deadline/Timeout)、传值(Value)。 它不是「任意全局状态袋」,不该往里塞业务对象;也不是给每个函数都强加的仪式,而是给「可能被取消的耗时代码」用的。 二、C++ 对照速查表 概念 C++ Go 关键差异 取消令牌 std::stop_token / 手写 atomic<bool> ctx.Done() + 树形传播 Go 一次 cancel 广播整棵子树 超时取消 std::stoptoken + 手算时间 context.WithTimeout(ctx, 3*time.Second) 一行完成「到时自动 cancel」 截止时间 steady_clock::now() + d 自己算 context.WithDeadline(ctx, t) Go 语义是绝对时刻 手动取消 手写 atomic<bool> + 到处检查 cancelFunc()(WithCancel 返回) cancel 是「广播」,不只本层 关联请求数据 线程局部 / 全局 map(危险) context.WithValue(parent, key, val) Go 类型安全 + 随 ctx 走 goroutine 收尾 jthread.request_stop() + join select { case <-ctx.Done(): ... } Go 惯例:非主路径用 select 监听取消 取消原因 无 ctx.Err() → context.Canceled / DeadlineExceeded 区分「被取消」与「超时」 上下文传播 无(要么传 token,要么全局) 函数第一参数约定 Go 靠约定 + vet 检查不传 nil 三、逐主题详解 3.1 两个根:context.Background() 与 context.TODO() package main import ( "context" "fmt" ) func main() { // Background:程序的「根上下文」,所有 context 树的起点 ctx := context.Background() fmt.Println("Background Err =", ctx.Err()) // nil(永远不会被取消) fmt.Println("Background Done =", ctx.Done()) // nil channel(永不关闭) // TODO:还没想好用哪个 context 时的占位,语义同 Background todo := context.TODO() fmt.Println("TODO 等价于 Background:", todo.Err() == nil) _ = ctx _ = todo } 规则: ...

2026 年 10 月 13 日

encoding/json 与配置文件

从 C++ 的 nlohmann/json 到 Go 的 encoding/json:结构体标签驱动的序列化 一、简述 Go 的 encoding/json 是标准库内置的 JSON 序列化/反序列化包。与 C++ 需要第三方库(nlohmann/json、rapidjson)不同,Go 通过**结构体字段标签(struct tag)**声明式地描述 JSON 映射关系,json.Marshal / json.Unmarshal 一键完成转换。 核心要点:Go 的 JSON 哲学是"结构体即 schema"——先用结构体定义数据形状,标签控制字段名、可选性、忽略规则。这也让配置文件读写(JSON/YAML)变得极其自然。 二、C++ 对照速查表 概念 C++ Go 关键差异 JSON 库 nlohmann/json 等第三方 encoding/json 标准库 开箱即用 对象映射 json j = json::parse(s) json.Unmarshal(data, &v) 需要目标结构体 序列化 j.dump() json.Marshal(v) 返回 []byte + error 字段改名 手动构造 struct tag `json:"name"` 声明式 忽略字段 手动排除 `json:"-"` 标签控制 可选字段 无 omitempty 零值不输出 动态 JSON json::object() / 数组 map[string]any / []any 用 any 容器 配置文件 Boost.PropertyTree / YAML 库 读 JSON 或第三方 YAML(gopkg.in/yaml.v3) JSON 为主 三、序列化与反序列化 3.1 基本用法 import ( "encoding/json" "fmt" ) type Person struct { Name string Age int } func main() { p := Person{Name: "Tom", Age: 30} // 序列化:结构体 → JSON data, err := json.Marshal(p) if err != nil { panic(err) } fmt.Println(string(data)) // {"Name":"Tom","Age":30} // 反序列化:JSON → 结构体 var p2 Person err = json.Unmarshal(data, &p2) if err != nil { panic(err) } fmt.Printf("%+v\n", p2) // {Name:Tom Age:30} } 3.2 结构体标签(关键) type Config struct { Server string `json:"server"` // 字段名映射为 server Port int `json:"port"` // 默认 0 Debug bool `json:"debug,omitempty"` // 零值时省略 Secret string `json:"-"` // 永远不参与 JSON Tags []string `json:"tags,omitempty"` // 空切片省略 } c := Config{Server: "localhost", Secret: "不输出"} data, _ := json.Marshal(c) fmt.Println(string(data)) // {"server":"localhost","port":0}(debug/tags 因 omitempty 省略,port 没有 omitempty 所以零值照样输出,secret 被 json:"-" 忽略) 标签规则: ...

2026 年 10 月 13 日

GMP 调度模型

C++ 里你要手写线程池、任务队列、负载均衡;Go 用 G/M/P 三件套在运行时层面把它全包了 一、简述 goroutine 不是 OS 线程,它比线程轻几个数量级。让几十万 goroutine 复用少数几个内核线程的,就是本章主角——GMP 调度模型。学完你会明白:go func() 背后发生了什么、为什么 goroutine 能在系统调用阻塞时"让位"、GOMAXPROCS 到底是什么、以及 M:N 用户态协程调度器是如何设计的。 核心要点:G(goroutine,任务)、M(Machine,OS 线程,工人)、P(Processor,调度上下文,工位)。P 的数量才是真正同时跑的 “线程数”,G 挂在 P 的本地队列上排队执行,M 抢到 P 才干活。运行时自动完成了 C++ 里你手写线程池 + 任务队列 + work-stealing 的全部工作。 二、C++ 对照速查表 概念 C++ Go 关键差异 并发执行单元 std::thread goroutine (G) std::thread 直接映射 OS 线程,G 是用户态任务 线程池 自写 / BS::thread_pool 等 内置 GMP Go 运行时自动复用 M,无需自建池 任务队列 std::queue<std::function> + mutex P 本地队列 runq 每个 P 有 256 长的本地队列,另有全局限队列 线程数量 自己定 / std::thread::hardware_concurrency GOMAXPROCS(默认 = CPU 核数) P 的数量决定并行度,M 可多于 P 线程阻塞迁移 无自动机制 M 阻塞时 P 移交 系统调用/锁阻塞时,P 被交给其它 M 继续执行 G 栈 固定大小(默认 1~8MB) 初始 2KB,动态增长 G 栈可扩容,按需分配 让出 CPU 无(靠 OS 时间片) runtime.Gosched() G 主动让出 P,避免饿死其它 G 抢占 内核时间片抢占 运行时信号抢占(Go 1.14+) 协作式 + 抢占式混合 work-stealing 手写 内置 空闲 P 偷其它 P 的队列任务,均衡负载 主线程 int main() m0 + g0 运行时专用线程和调度 goroutine 三、逐主题详解 3.1 三个字母分别是什么 ┌──────────────────────────────────────────────────────────────┐ │ GMP 关系示意 │ │ │ │ G (goroutine) M (machine) P (processor) │ │ ┌──────┐ ┌───────┐ ┌─────────────┐ │ │ │ 用户 │ │ 内核 │ │ 调度上下文 │ │ │ │ 代码 │──调度于──→ │ OS线程 │──绑定于──→│ 本地队列 │ │ │ │ 栈G │ │ 栈M │ │ runq[256] │ │ │ └──────┘ └───────┘ └─────────────┘ │ │ 任务 工人 工位 │ └──────────────────────────────────────────────────────────────┘ G(goroutine):一个待执行的函数 + 它的栈 + 上下文(寄存器现场)。创建的每个 go func() 都对应一个 G。它只是任务元数据,不是执行载体。G 结构体字段很长(src/runtime/runtime2.go),核心有栈描述 stack、当前函数 sched(保存 PC/SP)、状态 atomicstatus、所属 P 的指针、是否可被抢占等。 M(Machine):真实的 OS 线程,由内核调度。M 负责真正执行 G 的机器码。创建 M 要付出系统调用成本(约 1MB 内核栈 + 内核结构,线程栈),远贵于创建 G。M 通常不超过几千个,且有上限(受系统资源约束,默认 maxmcount=10000)。 P(Processor):你可以把它理解成"工位/许可令牌"。P 里有本地可运行队列 runq(长度 256)。G 想被调度运行,必须先被放进某个 P 的 runq,然后 P 绑定到某个 M 上执行队列里的 G。 P 的数量 = GOMAXPROCS(默认取 CPU 逻辑核数),它决定了同一时刻真正并行执行的 goroutine 上限。P 多了,并行度就高;P = 1 时行为接近单线程。 C++ 对照:M ≈ 一个真正运行在 CPU 上的线程;P ≈ 每个线程手头那个任务队列;G ≈ 任务队列里的任务项。你写线程池时是"一个线程,抢全局任务队列",Go 优化成"每线程一队列 + 偷取",减少锁竞争。 ...

2026 年 10 月 13 日

go mod 依赖管理

从 C++ 的 CMake+Conan/vcpkg 到 Go modules:官方集成、语义化版本与可复现构建 一、简述 Go 自 1.11 起引入 Go modules(常称 go mod)作为官方依赖管理工具,1.16 起默认启用。它把「依赖管理」内置到工具链里,一个 go.mod 文件声明模块与依赖,go.sum 锁定依赖的校验和,配合**语义化版本(v1.2.3)**和 GOPROXY 代理,实现可复现构建。 对于 C++ 开发者,可以把它理解为「CMake + Conan/vcpkg + 包清单 + 锁文件的四合一」:go.mod ≈ CMakeLists.txt + conanfile.txt,go.sum ≈ 锁文件,go get ≈ conan install,replace 指令 ≈ CMake 的本地路径引入。 核心要点:Go modules 不是「又一个包管理器」,而是语言官方、零配置文件的依赖解决方案。理解 go.mod 的四个指令(module / go / require / replace)就掌握了 90% 的日常操作。 二、C++ 对照速查表 概念 C++ Go 关键差异 构建系统 CMake / Makefile go build(内建) 无需写构建脚本 依赖清单 conanfile.txt / vcpkg.json go.mod 官方格式,随项目同时维护 依赖安装 conan install / vcpkg install go get / go mod tidy 不装到系统,进模块缓存 锁文件 Conan conan.lock(可选) go.sum 默认生成,锁定每行依赖版本+校验和 本地路径依赖 target_link_libraries 指向源码目录 replace ... => ../xxx 官方一等公民指令 语义化版本 Conan [requires] pkg/1.2.3 v1.2.3(标签含 v 前缀) Go 的版本就是 git tag 代理/镜像 自建 Artifactory / Nexus GOPROXY(如 goproxy.cn) 环境变量即可切换,无需配置仓库 私有仓库 CMake FetchContent + SSH GOPRIVATE + git 绕过代理直连 缓存目录 Conan 本地 cache $GOPATH/pkg/mod 全局共享,只读 vendor 机制 把第三方源码拷进仓库 go mod vendor 可选但常用,利于离线构建 三、逐主题详解 3.1 module 与 go.mod 三大字段 在项目根目录运行 go mod init 生成 go.mod: ...

2026 年 10 月 13 日

Go 内存模型:happens-before

C++ 有 std::atomic + 内存序(memory_order),Go 有更少的口子但边界更清楚——channel / Mutex / Once / atomic 各自建立 happens-before 一、简述 “变量加了锁就不会有竞态"这种直觉在并发世界里是危险的。真正需要回答的问题是:goroutine A 写了一个值,goroutine B 什么时候能确定读到它? Go 用一份官方文档——The Go Memory Model——精确规定了这些"什么时候”。它定义了 happens-before 关系:同步原语(channel、Mutex、WaitGroup、Once、atomic)在收发/加解锁/等待/原子操作之间建立序,从而保证内存可见性。理解它,你才知道为什么"不加同步 flag 读 bool"可能读到旧值,也才知道怎么写出不靠运气的并发程序。 核心要点:happens-before(HB)是"一个事件必须在另一个事件之前发生且其结果可见"的形式化保证。Go 的并发正确性全部建立在"你用了某个同步原语,它就给你对应的一条 HB 边"上。不用原语 = 没有边 = 编译器/硬件随便重排。 二、C++ 对照速查表 概念 C++ Go 关键差异 内存模型文档 [memory_order] 标准章节 The Go Memory Model(官方文档) C++ 需要你精确选 memory_order;Go 不给选项,只规定用了哪个同步原语获得哪条 HB 边 互斥锁 std::mutex 加锁/解锁 sync.Mutex Lock/Unlock C++ 还要额外配 atomic 才能安全读共享变量;Go 锁内一切可见 放松内存序 memory_order_relaxed 无对应概念 Go 只有两档:同步原语→对应 HB 保证,非同步→无保证 原子操作 std::atomic<T>(load/store/fetch等) sync/atomic Go 1.19 之前 atomic 只防撕裂不建 HB;1.19+ 视作同步点 初始化一次 std::once_flag sync.Once once.Do 完成后,所有 Do 返回点看到其写入 线程等待 std::thread::join sync.WaitGroup.Wait Wait 返回后看到各 Done 的写入 通信同步 mutex+cv 手写 channel 收发建立 HB,官方推荐优先 channel 主动读屏障 atomic_thread_fence 无直接对应 Go 用 atomic.Load/互斥代替 fence 悬而未决的"下次可见" 无 “稍后某个时刻可见"是合法但无保证 这是最常见的 bug 来源 数据竞争检测 TSan(第三方) go test -race 内建 干干净净的"竞态即报” 三、逐主题详解 3.1 你已经在 C++ 里见过的"序" C++ 的 memory_order 有三种强度:relaxed(无序)、acquire/release(只保证可见但不能越过)、seq_cst(全局一致)。Go 的模型把选择收敛成一条规则: ...

2026 年 10 月 13 日

Go 语言学习路线(C++ 开发者视角)

目录 1. 学习定位与目标 2. 从 C++ 到 Go 的思维转换 3. 阶段一:基础语法(第 1~2 周) 4. 阶段二:语言进阶(第 3~4 周) 5. 阶段三:并发编程(第 5~6 周) 6. 阶段四:工程化与工具链(第 7~8 周) 7. 阶段五:实战项目 8. 阶段六:深入内幕(进阶) 9. 推荐资源 10. 总结 1. 学习定位与目标 核心要点:Go 是为并发与工程效率而生的语言。它不是 C++ 的替代品,而是面向网络服务、云原生、分布式系统场景的现代选择。有 C++ 基础意味着你已经懂内存、指针、编译原理、并发难点——这些沉淀可以让你学 Go 的速度远超零基础者。 1.1 为什么学 Go 优点 说明 语法极简 没有类继承、泛型魔法(1.18 才加)、运算符重载,几小时能读完语法 编译快 没有模板实例化与复杂的 ODR,编译速度接近 C 语言 并发内建 goroutine + channel 是语言级能力,不是库 部署简单 静态编译,单个可执行文件,天然适合容器/云原生 标准库强大 网络、JSON、加密、压缩开箱即用,生态围绕服务端 工具链统一 官方格式化、测试、文档、依赖管理一条龙 1.2 对 C++ 开发者,Go 最大的"爽点" 没有内存泄漏焦虑:GC 自动回收,专注业务逻辑。 没有头文件割裂:一个 .go 文件即一个单元,import 即用。 没有 build 时间长:秒级编译。 没有模板错误地狱:泛型报错可读性好得多。 2. 从 C++ 到 Go 的思维转换 核心要点:这一节是全篇最关键的部分。C++ 功底在 Go 里既是资产也是包袱——资产是并发、内存模型、数据结构的直觉;包袱是"继承 + 异常 + 手动内存"的思维定式,需要刻意转换。 ...

2026 年 10 月 13 日

goroutine

Go 并发的基本执行单元:比线程轻量十倍的绿色协程,由 Go 运行时调度 一、C++ 对照速查表 概念 C++ Go 关键差异 并发执行单元 std::thread go func() goroutine 由运行时管理,不映射到 OS 线程 创建成本 ~1MB 栈 + 系统调用 ~2KB 栈,动态增长 goroutine 创建成本极低,可轻松创建上万 启动方式 std::thread t(func); t.join(); go func() Go 无需手动 join,用 channel/WaitGroup 同步 调度模型 1:1 映射 OS 线程 M:N 调度(GMP 模型) 运行时把 N 个 goroutine 调度到 M 个线程上 栈大小 固定(通常 1~8MB) 初始 2KB,按需增长/收缩 同一地址空间下可容纳海量 goroutine 终止方式 thread 析构或 detach 主 goroutine 结束则全部退出 goroutine 是 “fire and forget” 核心要点:goroutine 是 Go 并发的基石。创建一个 goroutine 只用一个 go 关键字,运行时自动处理调度、栈管理和线程复用。但 goroutine 之间需要通过 channel 或 sync 包 同步,否则会出现数据竞争。 ...

2026 年 10 月 13 日