Skip to content

Latest commit

 

History

History
531 lines (407 loc) · 10.1 KB

File metadata and controls

531 lines (407 loc) · 10.1 KB

2.1 Go 语言核心

📍 导航返回目录 | 下一节:C++核心


并发原语

Goroutine

原理:轻量级用户态线程,由 Go 运行时调度

// 启动 goroutine
go func() {
    fmt.Println("Hello from goroutine")
}()

// 等待 goroutine 完成
var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    // 执行任务
}()
wg.Wait()

特点

  • 初始栈大小仅 2KB(线程默认 8MB)
  • 由 GMP 模型调度:G(goroutine)、M(线程)、P(处理器)
  • 抢占式调度,避免饥饿

Channel

设计哲学:"不要通过共享内存来通信,而应该通过通信来共享内存"

// 无缓冲 channel(同步)
ch := make(chan int)

// 带缓冲 channel(异步)
ch := make(chan int, 10)

// 发送
ch <- 42

// 接收
value := <-ch

// 关闭
close(ch)

// range 遍历
for v := range ch {
    fmt.Println(v)
}

// select 多路复用
select {
case msg := <-ch1:
    fmt.Println("Received from ch1:", msg)
case msg := <-ch2:
    fmt.Println("Received from ch2:", msg)
case <-time.After(time.Second):
    fmt.Println("Timeout")
}

同步原语

sync.Mutex - 互斥锁

type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *Counter) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

sync.RWMutex - 读写锁

type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // 读锁
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // 写锁
    defer c.mu.Unlock()
    c.data[key] = value
}

使用场景:读多写少时使用 RWMutex,性能优于 Mutex

sync.WaitGroup - 等待组

func main() {
    var wg sync.WaitGroup
    
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Println("Worker", id)
        }(i)
    }
    
    wg.Wait() // 等待所有 goroutine 完成
}

sync.Once - 单次执行

var (
    instance *Singleton
    once     sync.Once
)

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{}
    })
    return instance
}

Context 上下文

作用

  • 传递请求作用域的值
  • 传递取消信号
  • 传递超时控制

四种 Context

// 1. 根 Context
ctx := context.Background()
ctx := context.TODO()

// 2. 带取消的 Context
ctx, cancel := context.WithCancel(parent)
defer cancel()

// 3. 带超时的 Context
ctx, cancel := context.WithTimeout(parent, 3*time.Second)
defer cancel()

// 4. 带截止时间的 Context
deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(parent, deadline)
defer cancel()

// 5. 带值的 Context
ctx = context.WithValue(parent, key, value)

实战示例:超时控制

func FetchDataWithTimeout(ctx context.Context, url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }
    
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    return io.ReadAll(resp.Body)
}

内存模型与并发陷阱

Happens-Before 原则

Go 保证以下 happens-before 关系:

  1. 初始化:包的 init 函数按依赖顺序执行
  2. Goroutine 创建go 语句 happens-before goroutine 执行
  3. Channel:发送 happens-before 接收完成
  4. :第 n 次 Unlock happens-before 第 n+1 次 Lock
  5. Onceonce.Do(f) 中的 f 执行 happens-before Do 返回

常见并发陷阱

陷阱1:循环变量捕获

// ❌ 错误示例
for i := 0; i < 5; i++ {
    go func() {
        fmt.Println(i) // 可能全部输出 5
    }()
}

// ✅ 正确方式1:传参
for i := 0; i < 5; i++ {
    go func(id int) {
        fmt.Println(id)
    }(i)
}

// ✅ 正确方式2:局部变量
for i := 0; i < 5; i++ {
    i := i // 创建新变量
    go func() {
        fmt.Println(i)
    }()
}

陷阱2:Goroutine 泄漏

// ❌ 错误示例:channel 发送端阻塞
func leak() {
    ch := make(chan int)
    go func() {
        ch <- 42 // 如果没有接收者,goroutine 永久阻塞
    }()
}

// ✅ 正确方式:使用 context 或缓冲 channel
func correct(ctx context.Context) {
    ch := make(chan int, 1) // 缓冲 channel
    go func() {
        select {
        case ch <- 42:
        case <-ctx.Done():
            return
        }
    }()
}

陷阱3:数据竞态

// ❌ 错误示例
var count int
for i := 0; i < 100; i++ {
    go func() {
        count++ // 数据竞态
    }()
}

// ✅ 正确方式:使用原子操作或锁
var count int64
for i := 0; i < 100; i++ {
    go func() {
        atomic.AddInt64(&count, 1)
    }()
}

检测数据竞态

go build -race     # 编译时启用竞态检测器
go test -race      # 测试时启用

垃圾回收(GC)

三色标记算法

  1. 白色:未被标记(待回收)
  2. 灰色:已标记但子对象未扫描
  3. 黑色:已标记且子对象已扫描

GC 调优

// 设置 GC 目标百分比(默认 100)
debug.SetGCPercent(50) // 堆增长 50% 触发 GC

// 手动触发 GC
runtime.GC()

// 查看 GC 统计
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
fmt.Printf("GC次数: %d, 暂停时间: %v\n", 
    stats.NumGC, stats.PauseNs[(stats.NumGC+255)%256])

减少 GC 压力

  • 使用对象池(sync.Pool
  • 减少小对象分配
  • 复用内存(slice、buffer)

标准库精选

net/http - HTTP 服务

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "time"
)

type Response struct {
    Message string `json:"message"`
    Time    string `json:"time"`
}

func main() {
    mux := http.NewServeMux()
    
    mux.HandleFunc("/api/hello", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        
        resp := Response{
            Message: "Hello, World!",
            Time:    time.Now().Format(time.RFC3339),
        }
        
        json.NewEncoder(w).Encode(resp)
    })
    
    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }
    
    log.Fatal(server.ListenAndServe())
}

database/sql - 数据库操作

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 配置连接池
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(10)
    db.SetConnMaxLifetime(5 * time.Minute)
    
    // 查询单行
    var name string
    err = db.QueryRow("SELECT name FROM users WHERE id = ?", 1).Scan(&name)
    
    // 查询多行
    rows, err := db.Query("SELECT id, name FROM users WHERE age > ?", 18)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()
    
    for rows.Next() {
        var id int
        var name string
        if err := rows.Scan(&id, &name); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("ID: %d, Name: %s\n", id, name)
    }
    
    // 事务
    tx, err := db.Begin()
    if err != nil {
        log.Fatal(err)
    }
    
    _, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = ?", 1)
    if err != nil {
        tx.Rollback()
        log.Fatal(err)
    }
    
    _, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = ?", 2)
    if err != nil {
        tx.Rollback()
        log.Fatal(err)
    }
    
    tx.Commit()
}

最佳实践

1. 错误处理

// ✅ 推荐:显式错误处理
if err != nil {
    return fmt.Errorf("failed to open file: %w", err) // Go 1.13+ 错误包装
}

// ❌ 避免:忽略错误
file, _ := os.Open("file.txt") // 不推荐

2. 接口设计

// ✅ 小接口原则
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// 组合使用
type ReadWriter interface {
    Reader
    Writer
}

3. 命名规范

  • 包名:小写,简短,不使用下划线
  • 导出标识符:大写开头
  • 私有标识符:小写开头
  • 接口名:单方法接口用 -er 后缀(Reader, Writer)

4. 代码组织

project/
├── cmd/              # 可执行程序入口
│   └── server/
│       └── main.go
├── internal/         # 私有代码(不可被外部导入)
│   ├── handler/
│   ├── service/
│   └── repository/
├── pkg/              # 公共库(可被外部导入)
│   └── util/
├── api/              # API 定义(protobuf, OpenAPI)
├── configs/          # 配置文件
├── scripts/          # 脚本
└── go.mod

本章小结

Go 语言以其简洁的并发模型和高效的运行时成为云原生时代的首选语言。

关键要点

  • ✅ Goroutine 和 Channel 是 Go 并发的基石
  • ✅ 理解 Context 对于超时控制和取消传播至关重要
  • ✅ 避免常见并发陷阱(变量捕获、goroutine泄漏、数据竞态)
  • ✅ 标准库设计优雅,生产可用

扩展阅读

  • 《Go语言圣经》
  • 《Go并发编程实战》
  • Effective Go

💡 思考题

  1. 无缓冲 channel 和带缓冲 channel 有什么区别?
  2. 什么情况下会导致 goroutine 泄漏?如何避免?
  3. defer 的执行顺序是怎样的?

⏮️ 返回目录 | ⏭️ 下一节:C++核心