原理:轻量级用户态线程,由 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(同步)
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")
}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
}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
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 完成
}var (
instance *Singleton
once sync.Once
)
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{}
})
return instance
}- 传递请求作用域的值
- 传递取消信号
- 传递超时控制
// 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)
}Go 保证以下 happens-before 关系:
- 初始化:包的 init 函数按依赖顺序执行
- Goroutine 创建:
go语句 happens-before goroutine 执行 - Channel:发送 happens-before 接收完成
- 锁:第 n 次 Unlock happens-before 第 n+1 次 Lock
- Once:
once.Do(f)中的 f 执行 happens-beforeDo返回
// ❌ 错误示例
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)
}()
}// ❌ 错误示例: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
}
}()
}// ❌ 错误示例
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 目标百分比(默认 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)
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())
}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()
}// ✅ 推荐:显式错误处理
if err != nil {
return fmt.Errorf("failed to open file: %w", err) // Go 1.13+ 错误包装
}
// ❌ 避免:忽略错误
file, _ := os.Open("file.txt") // 不推荐// ✅ 小接口原则
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
}- 包名:小写,简短,不使用下划线
- 导出标识符:大写开头
- 私有标识符:小写开头
- 接口名:单方法接口用
-er后缀(Reader, Writer)
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
💡 思考题:
- 无缓冲 channel 和带缓冲 channel 有什么区别?
- 什么情况下会导致 goroutine 泄漏?如何避免?
defer的执行顺序是怎样的?