我要提问
GO TUTORIAL

Go 语言系统教程

从语法、结构体、接口到 Goroutine、Channel 与并发原语,掌握云原生时代的并发编程。

Go 语言系统教程

Go 语言系统教程封面

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 基本类型包括 intfloat64stringbool,以及复合类型 arrayslicemapstructslice(切片)是动态数组,日常用得最多。

结构体

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 包提供了 WaitGroupMutexOnce 等并发原语。

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 modgo fmtgo 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 用到一个真实接口服务里,遇到并发或报错问题可到 后端问答报错解决 提问。

返回教程中心