Go(Golang)是 Google 推出的静态强类型、编译型语言,主打简洁语法、原生并发与极速编译,是云原生(Docker / Kubernetes / etcd)时代的首选语言。本教程按"语法 → 结构体 → 接口 → Goroutine → Channel → 错误处理 → Web 实战"展开。
一、环境与基础语法
从官网安装 Go 后,用 go mod init 初始化模块。Go 用 go run 直接运行、go build 编译。每个 Go 程序从 main 包的 main 函数开始。
Hello World
package main
import "fmt"
func main() {
name := "mrgr.cn" // 短变量声明(函数内)
fmt.Printf("hello %s\n", name)
}
Go 用 := 做短变量声明(仅函数内),var 用于显式声明。声明但未使用的变量会编译报错,这是 Go 强制的工程规范。
提示:Go 没有分号结尾(编译器自动插入),左大括号必须与关键字同行,否则会报语法错误。这是 Go 风格的强制约定。
二、数据类型与结构体
Go 基本类型包括 int、float64、string、bool,以及复合类型 array、slice、map、struct。slice(切片)是动态数组,日常用得最多。
结构体
type User struct {
ID int
Name string
Age int
}
u := User{ID: 1, Name: "mrgr", Age: 5}
fmt.Println(u.Name)
// 方法:绑定到结构体
func (u User) Greet() string {
return "hi, I'm " + u.Name
}
slice 与 map
nums := []int{1, 2, 3}
nums = append(nums, 4) // 追加
m := map[string]int{"a": 1, "b": 2}
for k, v := range m { // 遍历
fmt.Println(k, v)
}
三、接口与类型系统
Go 的接口是隐式实现——只要类型实现了接口定义的全部方法,就自动满足该接口,无需 implements 声明。这叫"鸭子类型"的静态版。
定义与实现
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { // Dog 自动实现 Speaker
return d.Name + ": 汪汪"
}
func greet(s Speaker) {
fmt.Println(s.Speak())
}
greet(Dog{Name: "旺财"})
空接口与类型断言
interface{}(Go 1.18+ 可写 any)是空接口,任意类型都满足。配合类型断言 v, ok := x.(T) 做类型判断。
- 接口组合:接口可嵌入其他接口,组合成更大接口。
- 值接收者 vs 指针接收者:指针接收者的方法集包含值接收者,反之不成立。
- 面向接口编程:依赖抽象而非具体类型,便于测试与替换。
四、Goroutine 与调度模型
Goroutine 是 Go 的轻量级线程,由 Go 运行时调度(GMP 模型),创建成本远低于系统线程(几 KB 栈),可以轻松开数十万个。
启动 Goroutine
go func(msg string) {
fmt.Println(msg)
}("hello goroutine")
只需在函数调用前加 go 关键字即可并发执行。但 Goroutine 之间默认无同步,主程序退出时未完成的 Goroutine 会被直接终止。
GMP 调度模型
Go 调度器由三部分组成:G(Goroutine)、M(Machine,系统线程)、P(Processor,逻辑处理器,持有本地运行队列)。P 的数量由 GOMAXPROCS 决定(默认等于 CPU 核数),M 阻塞时调度器会转移 P 给其他 M,保持 CPU 不闲。
要点:Goroutine 是协作式抢占调度,函数调用时检查抢占标志。纯计算密集的 Goroutine 不主动让出 CPU 时,Go 1.14+ 的异步抢占也能强制调度。
五、Channel 与并发原语
Go 推崇"不要通过共享内存通信,而要通过通信共享内存"。Channel 是 Goroutine 间通信的管道,天然线程安全。
无缓冲与有缓冲 Channel
// 无缓冲:发送阻塞直到有接收者
ch := make(chan int)
go func() {
ch <- 42 // 发送
}()
v := <-ch // 接收
fmt.Println(v)
// 有缓冲:缓冲未满时发送不阻塞
buf := make(chan string, 2)
buf <- "a"
buf <- "b"
close(buf)
for s := range buf { // 遍历直到关闭
fmt.Println(s)
}
select 与并发原语
select 用于多路复用 Channel,类似 switch 但针对通信操作。Go 还在 sync 包提供了 WaitGroup、Mutex、Once 等并发原语。
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Println("task", n)
}(i)
}
wg.Wait() // 等待全部完成
Mutex/RWMutex:互斥锁与读写锁,保护共享状态。sync.Once:保证初始化代码只执行一次(单例)。context:传递截止时间、取消信号与请求作用域值,并发任务必备。
六、错误处理与测试
Go 没有 try/catch,错误通过函数返回值显式传递。约定函数最后一个返回值为 error,调用方必须检查。
错误处理
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("除数不能为 0")
}
return a / b, nil
}
result, err := divide(10, 0)
if err != nil {
log.Println(err)
return
}
自定义错误可用 fmt.Errorf 包装(%w),再用 errors.Is / errors.As 解包判断,实现错误链。
测试
Go 内置 testing 包,测试文件以 _test.go 结尾,测试函数签名 func TestXxx(t *testing.T),用 go test 运行。
func TestDivide(t *testing.T) {
got, err := divide(10, 2)
if err != nil || got != 5 {
t.Fatalf("expected 5, got %d, err %v", got, err)
}
}
七、Web 服务与工程实践
Go 标准库 net/http 就能写出高性能 Web 服务,无需第三方框架。配合 go mod、go fmt、go vet 等工具链,工程化体验非常顺滑。
HTTP 服务
package main
import (
"encoding/json"
"net/http"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "mrgr"
}
json.NewEncoder(w).Encode(map[string]string{
"msg": "hello " + name,
})
})
http.ListenAndServe(":8080", nil)
}
实际项目常选用 Gin、Echo 等框架获得路由分组、中间件、参数绑定等能力。Go 服务的编译产物是单一二进制,配合 Docker 部署非常轻量,是云原生后端的理想选择。
本教程到此结束。建议结合 实战项目 把 Go 用到一个真实接口服务里,遇到并发或报错问题可到 后端问答 或 报错解决 提问。