Golang中暂停代码执行的各种方法

Golang中暂停代码执行的各种方法 以下两个 goroutine 似乎执行完全相同的操作:

goroutine 1:

	go func() {
		time.Sleep(50 * time.Millisecond)
        // 需要在 50 毫秒后执行的代码
	}()

goroutine 2:

	go func() {
		select {
		case <-time.After(50 * time.Millisecond):
			// 需要在 50 毫秒后执行的代码
		}
	}()

在我看来,第二个 goroutine 的语法非常糟糕。它更难阅读(除非你之前见过并能认出它),而且输入起来也更长。它们之间有区别吗?甚至有必要“记住”第二种暂停执行的方式(goroutine 2)吗?


更多关于Golang中暂停代码执行的各种方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

可以简单地等待第一个被使用,但第二个可以等待其他通道,并使用 case After 作为超时机制。

更多关于Golang中暂停代码执行的各种方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我会选择第一种方式。第二种方式只有在你的 select 语句中还有其他通道接收操作时才有意义,例如执行某个操作并在 50 毫秒后超时。

当然,如果你只使用一个带有 case <-time.After(): 的 case,那实际上是一样的。在这种情况下,你应该使用 time.Sleep()

这两者是有区别的。第一种情况是休眠(并且完全被阻塞),而第二种情况是等待。你可以添加另一个 case 来等待第二个通道(例如 case <-context.Done():),从而中断这种“休眠”(实际上是等待),或者那个另一个通道可以是操作系统信号处理。

@skillian 好的,那么主要区别在于通道。这说得通,我对通道的了解还非常浅,所以之前没看出来。

@kron4eg 当第二个 goroutine 除了等待之外什么都不做时,休眠等待有实际区别吗?这是否意味着 goroutine 2 实际上也被阻塞了?

请选择方案二,因为:

  • 更容易添加第二种情况来取消等待
  • 使用的资源比 time.Sleep 少(不过不确定,请了解 time.Sleep 工作原理的人详细说明)
  • 语法能更清晰地传达意图;我的看法似乎有违直觉,但这是标准语法(在处理通道和同步时,time.Sleep 在我看来确实很糟糕)
  • 你更有机会恰好等待 50 毫秒,而不是更久

在Go语言中,这两种方式确实都能实现延迟执行,但它们在内存管理和使用场景上有重要区别。

goroutine 1:使用 time.Sleep()

go func() {
    time.Sleep(50 * time.Millisecond)
    // 50毫秒后执行的代码
    fmt.Println("Sleep: 执行完成")
}()

goroutine 2:使用 time.After()

go func() {
    select {
    case <-time.After(50 * time.Millisecond):
        // 50毫秒后执行的代码
        fmt.Println("After: 执行完成")
    }
}()

主要区别:

1. 内存泄漏风险

time.After() 创建的定时器在超时前不会被垃圾回收,如果放在长时间运行的循环中可能导致内存泄漏:

// 错误示例 - 可能导致内存泄漏
func processMessages() {
    for {
        select {
        case msg := <-messageChan:
            process(msg)
        case <-time.After(50 * time.Millisecond):
            // 每次循环都创建新的定时器,旧的不会被立即回收
            doTimeoutWork()
        }
    }
}

// 正确做法 - 使用 time.NewTimer()
func processMessagesFixed() {
    timer := time.NewTimer(50 * time.Millisecond)
    defer timer.Stop()
    
    for {
        timer.Reset(50 * time.Millisecond)
        select {
        case msg := <-messageChan:
            process(msg)
        case <-timer.C:
            doTimeoutWork()
        }
    }
}

2. 取消机制

time.After() 无法取消,而 time.NewTimer() 可以:

func cancellableDelay() {
    timer := time.NewTimer(50 * time.Millisecond)
    
    go func() {
        select {
        case <-timer.C:
            fmt.Println("定时器触发")
        case <-cancelChan:
            timer.Stop()
            fmt.Println("定时器已取消")
        }
    }()
}

3. 在 select 中的超时控制

time.After() 在需要超时控制的 select 语句中更简洁:

func fetchWithTimeout() {
    select {
    case result := <-asyncFetch():
        fmt.Println("获取成功:", result)
    case <-time.After(100 * time.Millisecond):
        fmt.Println("请求超时")
    }
}

推荐用法:

  1. 简单延迟执行 - 使用 time.Sleep()
go func() {
    time.Sleep(50 * time.Millisecond)
    doSomething()
}()
  1. select 超时控制 - 使用 time.After()
select {
case data := <-ch:
    return data
case <-time.After(50 * time.Millisecond):
    return nil, errors.New("超时")
}
  1. 需要取消的定时器 - 使用 time.NewTimer()
timer := time.NewTimer(50 * time.Millisecond)
defer timer.Stop()

select {
case <-timer.C:
    // 定时触发
case <-ctx.Done():
    // 上下文取消
}

性能对比示例:

func benchmarkTimers() {
    // time.Sleep 性能测试
    start := time.Now()
    time.Sleep(50 * time.Millisecond)
    fmt.Printf("Sleep耗时: %v\n", time.Since(start))
    
    // time.After 性能测试
    start = time.Now()
    <-time.After(50 * time.Millisecond)
    fmt.Printf("After耗时: %v\n", time.Since(start))
}

总结:两种方式都有其适用场景。time.Sleep() 适用于简单的延迟执行,而 time.After() 更适合在 select 语句中实现超时控制,但需要注意在循环中使用时的内存管理问题。

回到顶部