首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >《Go小技巧&易错点100例》第四十三篇

《Go小技巧&易错点100例》第四十三篇

原创
作者头像
闫同学
发布2025-09-18 19:06:42
发布2025-09-18 19:06:42
880
举报

本期分享:

1. selectfor循环的死锁问题

2. defer改变函数返回值

3. 如何优雅地停止 goroutine


selectfor 循环的死锁问题

很多同学第一次写 Go 的并发代码时,都会尝试用 for + select 来监听 channel:

代码语言:go
复制
func main() {
    ch := make(chan int)
    for {
        select {
        case v := <-ch:
            fmt.Println("receive:", v)
        }
    }
}

这段代码有什么问题?

👉 如果 ch 永远没人写入,就会发生 死锁。因为 select 里没有 default 分支,case 又不满足,程序就会卡死。

正确姿势

方式一:加 default(非阻塞尝试):

代码语言:go
复制
for {
    select {
    case v := <-ch:
        fmt.Println("receive:", v)
    default:
        // 没数据就先干点别的
        time.Sleep(10 * time.Millisecond)
    }
}

方式二:关闭 channel 退出

代码语言:go
复制
go func(ch chan int) {
    for i := 0; i < 3; i++ {
        ch <- i
    }
    close(ch)
}(ch)

for v := range ch {
    fmt.Println("receive:", v)
}

总结一句话:for+select 一定要考虑退出条件,否则就等着卡死吧。

defer 改变函数返回值

defer 语句可能改变函数的返回值。defer 中的参数在声明时就被求值,而非函数执行时。先上代码:

代码语言:go
复制
func test1() int {
    var i int = 1
    defer func() {
        i++
    }()
    return i
}

结果是多少?

👉 答案是 1,而不是 2

为什么?

因为 Go 的返回值在执行 return 时就已经确定了,defer 只是在 return 之后再执行。

那有没有办法让 defer 改变返回值呢?

有的,但必须用 命名返回值

代码语言:go
复制
func test2() (i int) {
    i = 1
    defer func() {
        i++
    }()
    return i
}

结果就是 2

所以记住口诀:

  • 匿名返回值defer 无法改变最终结果。
  • 命名返回值defer 可以操作并影响返回值。

这就是 Go 的“延迟收尾”机制,一个小小的关键字,背后却有很多学问。

如何优雅地停止 goroutine

写过 Go 的人都知道:goroutine 可以轻松启动,但停止却不是那么容易。

比如下面的代码:

代码语言:go
复制
func worker() {
    for {
        fmt.Println("working...")
        time.Sleep(time.Second)
    }
}

func main() {
    go worker()
    time.Sleep(3 * time.Second)
    fmt.Println("main exit")
}

问题:main 退出了,worker 却还在那边跑,像个“孤魂野鬼”。

常见几种停止方式

channel 作为退出信号

代码语言:go
复制
func worker(stop chan struct{}) {
    for {
        select {
        case <-stop:
            fmt.Println("worker stopped")
            return
        default:
            fmt.Println("working...")
            time.Sleep(time.Second)
        }
    }
}

func main() {
    stop := make(chan struct{})
    go worker(stop)
    time.Sleep(3 * time.Second)
    close(stop) // 发送退出信号
}

context 控制(推荐,优雅且通用)

代码语言:go
复制
func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("worker stopped")
            return
        default:
            fmt.Println("working...")
            time.Sleep(time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx)
    time.Sleep(3 * time.Second)
    cancel() // 结束 goroutine
}

用标志位(不推荐,线程安全问题多,容易踩坑)

小结

今天我们一起回顾了三个看似简单但容易掉坑的问题:

  1. for+select 容易死锁,必须有退出条件。
  2. defer 对返回值的影响取决于是否使用命名返回值。
  3. goroutine 的退出需要优雅设计,推荐使用 context 来做统一控制。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • select 和 for 循环的死锁问题
    • 正确姿势
  • defer 改变函数返回值
  • 如何优雅地停止 goroutine
    • 常见几种停止方式
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档