news 2026/7/21 15:55:47

GO 语言基础

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GO 语言基础

前言

在Go语言诞生之前,谷歌主要使用C++和Java进行系统编程和后端服务开发。这些语言虽然功能强大,但也有显著的缺陷:

  • 编译速度慢:C++的大型代码库需要很长的编译时间,这在快速开发和迭代中是一个严重的瓶颈。
  • 复杂的依赖管理:大型项目中,C++和Java的依赖管理和编译链接过程非常复杂,导致开发和维护困难。
  • 并发处理的复杂性:随着互联网服务的规模增长,并发处理成为关键问题。然而,C++和Java在处理并发时需要大量复杂的代码,容易出现错误。

简单的GO项目

一个完整最小的GO项目结构如下

go-test/ 项目根目录 ├── cmd/ │ └── go-test/ │ └── main.go 程序启动入口,负责调用内部 CLI 逻辑 ├── internal/ 项目内部实现目录,禁止外部模块直接引用 │ └── cli/ │ ├── cli.go CLI 主流程、参数分发、hello/greet 等通用命令 │ └── ws.go WebSocket 相关命令实现,如 ws server / ws client ├── go.mod Go 模块定义文件,声明模块名、Go 版本、直接依赖 ├── go.sum Go 依赖校验文件,记录依赖哈希,保证下载内容一致 └── README.md 项目介绍、目录说明、运行方式和扩展说明

项目主要功能,在go-test目录下:

go run ./cmd/go-test hello go run ./cmd/go-test greetsum123.5go run ./cmd/go-test ws server go run ./cmd/go-test ws client--message"hello from go client"

编译速度

Go 编译快,主要靠 3 件事:语言本身简单、依赖模型简单、工具链统一。

可以理解:Go 从语言设计开始,就在避免那些会让编译器“反复做重活”的机制。

  1. 没有#include那种文本展开,而是import "fmt",这不是把 fmt 的源码文本拷进来,而是:

    • 依赖按 包 管理, Go 的基本单位是 package
    • 包先编译好,会产出这个包的编译结果和导出信息
    • 别的包依赖它时,不需要重新完整分析它的源码
      • 同包文件一起编译
      • 包和包之间通过 import 连接
        - 下游包主要看上游包的导出接口
  2. Go 的语言特性比较克制, Go 语法和类型系统是故意做得比较简的。

    • 宏系统
    • 过度花哨的编译期计算 (Java 中的 Lombok)
    • 非常深的继承体系
    • 大量隐式规则 (JAVA 中的 annotation processor)
  3. 依赖图是强约束的,而且禁止循环依赖,Go 的包依赖必须是一个 DAG,也就是有向无环图。 也就是说:

    • A 可以依赖 B
    • B 可以依赖 C
    • 但 C 不能再回头依赖 A

所以 Go的“编译速度快的原因” 不是要让编译器特别聪明,而是尽量别给编译器制造太多麻烦。另外“官方工具链统一”,也少了很多额外损耗 .

go build 自带构建缓存

  • 没变的包,不重编
  • 只重编改过的包,以及依赖它的包
  • 很多包还能并行编译

依赖管理

一个普通 Go 项目,依赖管理核心通常就这两个文件

  • go.mod 依赖声明
    ## 模块名module go-test## Go 版本go1.22## 直接依赖require github.com/gorilla/websocket v1.5.3
  • go.sum 依赖较验
    ## github.com/gorilla/websocket 这个模块的 v1.5.3 版本,源码包整体内容的校验值是这个## GO要知道下载到的源码内容有没有变github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=## github.com/gorilla/websocket 这个模块的 v1.5.3 版本中,它自己的 go.mod 文件,校验值是这## GO要知道 依赖自己的模块声明文件有没有变github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

GO的依赖管理的核心理念是“我依赖谁,版本是什么,校验值是什么”,并不承受包管理之外的 “建生命周期、插件平台、企业治理 ”, 以MAVEN为例

维度GoMaven
核心文件go.mod+go.sum主要是pom.xml,但常连着 parent POM、profiles、plugins、repositories 等
项目身份定义module xxxgroupId + artifactId + version
Go/Java 版本声明go 1.22直接写在go.mod常放在propertiesmaven-compiler-plugin
直接依赖声明require 模块 版本<dependency>+groupId/artifactId/version
依赖声明语法复杂度很轻,接近一行一句话XML 层级更重
依赖校验go.sum明确记录哈希主要依赖 Maven 仓库机制和 checksum
测试依赖管理也走go.mod/go.sum,但靠*_test.go+go test区分通过scope=test区分
是否有显式 scope没有 Maven 那套compile/test/runtime/provided有完整 scope 体系
测试依赖的区分方式靠测试文件命名和测试命令路径靠依赖声明里的scope=test
版本治理层次相对扁平,当前模块直接require常有 parent POM、dependencyManagement、继承覆盖
本地联调依赖replace old/module => ../local-module常靠本地安装、多模块、私服、改版本
排除问题依赖exclude module version<exclusions>更偏排除某条传递依赖链上的包
工具链入口go build/go test/go mod tidy/go getmvn package/mvn test/ 插件 goal 等

并发处理

GO中的并发设计,趋向于把“并发流程表达清楚”, 即通过以下组件

Go 概念Go 常见写法Java 中最接近的对应物主要作用
goroutinego f()ExecutorService.submit(...)/Thread.startVirtualThread(...)启动一个并发任务
channelch <- v/v := <-chBlockingQueue/SynchronousQueue/Future/CompletableFuture在并发任务之间传数据、同步协作
selectselect { ... }CompletableFuture.anyOf(...)/BlockingQueue.poll(...)/Selector同时等待多个事件,谁先就绪就处理谁
contextcontext.WithCancel/WithTimeout/WithDeadlineFuture.cancel(...)/interrupt/ 超时 API / 自定义上下文对象取消、超时、截止时间、请求链路传递
WaitGroupwg.Add()/wg.Done()/wg.Wait()CountDownLatch/CompletableFuture.allOf(...)等一批并发任务执行结束
mutexsync.Mutexsynchronized/ReentrantLock保护共享变量、临界区互斥
atomicsync/atomicAtomicInteger/AtomicLong/VarHandle做无锁原子操作
worker poolgoroutine + chan + WaitGroupThreadPoolExecutor + BlockingQueue + Future控制并发度,批量处理任务
timeoutselect + time.After(...)/context.WithTimeout(...)Future.get(timeout)/orTimeout(...)/ScheduledExecutorService给任务或等待过程加超时
graceful shutdownclose(ch)/cancel()/wg.Wait()shutdown()/shutdownNow()/awaitTermination()/ 中断机制优雅退出后台任务和服务

再由 runtime 再帮GO调度。即Go 通过 runtime 统一并发标准的优势主要是:

  1. 并发模型更统一:启动、通信、等待、取消、退出是一套体系
  2. 阻塞代码也能高并发:runtime 帮你做调度和 netpoll
  3. 更适合表达任务协作:channel + select 很自然
  4. 取消链路标准化:context 贯穿请求到下游
  5. 更适合服务端工程:团队更容易形成一致写法

go runtime 简析

golang 的 runtime 在 golang 中的地位类似于 Java 的虚拟机,不过 go runtime 不是虚拟机. golang 程序生成可执行文件在指定平台上即可运行,效率很高, 它和 c/c++ 一样编译出来的是二进制可执行文件. 它是像服务代码的一部分,跟着项目一起编译、一起部署、一起运行

运行 golang 的程序并不需要主机安装有类似 Java 虚拟机之类的东西,那是因为在编译时,golang 会将 runtime 部分代码链接进去.


golang 的 runtime 核心功能包括以下内容:

  • 协程(goroutine)调度(并发调度模型)
  • 垃圾回收(GC)
  • 内存分配

内存分配

Go runtime 的内存分配就采用了 Tcmalloc 算法.其核心思想是多级管理,从而降低锁的粒度.


Go 程序在启动时,会首先向系统申请一块内存(虚拟地址空间),然后自己切成小块进行管理. 将申请的内存,分成 3 个区域,spans、bitmap、arena,这三个区域的作用如下.

  • arena: 就是堆区,go runtime 在动态分配的内存都在这个区域,并且将内存块分成 8kb 的页,一些组合起来的称为 mspan,成为 go 中内存管理的基本单元,这种连续的页一般是操作系统的内存页几倍大小.
  • bitmap: 顾名思义,用来标记堆区使用的映射表,它记录了哪些区域保存了对象,对象是否包含指针,以及 GC 的标记信息.
  • spans: 存放 mspan 的指针,根据 spans 区域的信息可以很容易找到 mspan. 它可以在 GC 时更快速的找到的大块的内存 mspan.

垃圾回收(GC)

垃圾回收机制是编程语言的重要部分,它影响到程序的长久稳定运行. Java、Python 等语言都有自己的垃圾回收机制,而不需要像 c/c++一样由程序员管理,可以避免大量的内存泄漏.

GO也采用了与JAVA相似的《三色标记清扫法》来处理GC

协程调度模型

golang 语言相比其它语言有一个特殊之处,它实现了自己的调度模块,并不完全是由计算机操作系统进行调度的(进程、线程). golang 原生支持协程 goroutine,区别于线程、进程. goroutine 的调度由 go runtime 进行,这也是 golang 并发效率高的原因之一.


go 在处理协程上,使用了 GPM 调度模型,从而支持高效的并发调度. 内核线程与逻辑处理器是多对多的关系即 M:N. 从而提升并发效率. GPM 各个模块的解释如下:

  • G: 即 Goroutine,更轻量级的线程,保存着上下文信息.
  • P: Processor,是逻辑处理器. 将 goroutine 绑定逻辑处理器 P 的本地队列后,才会被调度. Processor 提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等
  • M: 它才是真正的计算资源,是系统线程.
  • 全局队列(Global Run Queue): 未分配 Processor 的 Goroutine 保存在全局队列中. Processor 或 M 都可以从全局队列中取出 G .
  • 本地队列(Local Run Queue): 是 Processor 的队列,当队列为空时,会从全局队列或其它队列补充 Goroutine.
  • sysmon 协程: go runtime 会创建一个 sysmon 协程. 它会定期唤醒检查 goroutine 和 processor,确保 goroutine 不会长期占用 CPU 以及 Processor 可以被执行.

总结

Go的核心思想,就是用统一的默认约定、内置的 runtime 能力和简化的工程路径,替开发者收掉大量“非业务复杂度”,让程序员更专注于业务流程和系统协作本身。

Go 不是想给你最多选择,而是想给你一条默认就比较对的路。

站在巨人的肩膀上

《go runtime 简析》
《万字长文深入浅出 Golang Runtime》

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/20 10:19:11

Steam成就管理:如何用5分钟完成原本需要200小时的游戏成就

Steam成就管理&#xff1a;如何用5分钟完成原本需要200小时的游戏成就 【免费下载链接】SteamAchievementManager A manager for game achievements in Steam. 项目地址: https://gitcode.com/gh_mirrors/st/SteamAchievementManager 你是否曾经因为某个Steam成就过于困…

作者头像 李华
网站建设 2026/7/20 10:19:09

游戏项目中的程序化生成(PCG):五层工作的耦合问题

前言 我进入游戏行业的时候&#xff0c;正值《塞尔达传说&#xff1a;旷野之息》发售不久&#xff0c;让业界感受到了大世界游戏的魅力&#xff1b;而 Far Cry 5 和 Ghost Recon 又在 GDC 上分享了他们使用 PCG 技术来生成大世界的经验。我想正是在这个时候&#xff0c;游戏行…

作者头像 李华
网站建设 2026/7/20 10:19:06

TI AM263P ADC架构解析:从SOC机制到硬件后处理的工业数据采集实战

1. 项目概述与核心价值在工业控制、电机驱动或者高精度传感器数据采集的项目里&#xff0c;我们常常会碰到一个绕不开的核心问题&#xff1a;如何把现实世界里的连续模拟信号&#xff0c;比如电机的相电流、母线电压、温度传感器的输出电压&#xff0c;快速、准确、可靠地转换成…

作者头像 李华
网站建设 2026/7/20 10:18:56

NLP流水线云上部署实战:AWS EC2+Sentence-Transformers端到端落地

1. 项目概述&#xff1a;为什么一个真实的NLP流水线必须“长在云上”我带过六届实习生&#xff0c;也帮三家公司从零搭过生产级NLP系统。每次新人问“本地Jupyter跑得好好的&#xff0c;为啥非得上云”&#xff0c;我都会先让他们试一次&#xff1a;用笔记本跑完5万条推文的语义…

作者头像 李华
网站建设 2026/7/20 10:18:00

C++实现Canny边缘检测:从原理到代码的完整指南

1. 项目概述&#xff1a;从像素到轮廓的跨越在图像处理的世界里&#xff0c;边缘检测是连接原始像素数据与高层视觉理解的桥梁。想象一下&#xff0c;你拿到一张模糊的照片&#xff0c;如何让计算机“看清”照片里物体的轮廓&#xff1f;这就是边缘检测要解决的核心问题。它不关…

作者头像 李华