Go语言 如何正确停止计时器?

ibps3vxo  于 2022-12-16  发布在  Go
关注(0)|答案(4)|浏览(271)
var timer *time.Timer

func A() {
    timer.Stop() // cancel old timer
    go B() // new timer
}

func B() {
    timer = time.NewTimer(100 * time.Millisecond)
    select {
    case <- timer.C:
    // do something for timeout, like change state
    }
}

函数A和B都在不同的goroutine中。
假设A在一个RPC goroutine中,当应用程序收到RPC请求时,它会取消B中的旧计时器,并在另一个goroutine中启动一个新计时器。
医生说:
停止并不关闭通道,以防止从通道的读取不正确地继续。
那么,如何打破B中的选择来避免goroutine泄漏呢?

qni6mghb

qni6mghb1#

使用额外的独立抵消信号。既然已经有了select语句,那么另一个通道显然是一个选择:

import "time"

var timer *time.Timer
var canceled = make(chan struct{})

func A() {
    // cancel all current Bs
    select {
    case canceled <- struct{}{}:
    default:
    }   

    timer.Stop()

    go B()       // new timer
}

func B() {
    timer = time.NewTimer(100 * time.Millisecond)
    select {
    case <-timer.C:
        // do something for timeout, like change state
    case <-canceled:
        // timer aborted
    }
}

注意,所有的A和B都在竞争计时器值,使用上面的代码,A不需要停止计时器,所以你不需要全局计时器,消除了竞争:

import "time"

var canceled = make(chan struct{})

func A() {
    // cancel all current Bs
    select {
    case canceled <- struct{}{}:
    default:
    }

    go B()
}

func B() {
    select {
    case <-time.After(100 * time.Millisecond):
        // do something for timeout, like change state
    case <-canceled:
        // aborted
    }
}
hzbexzde

hzbexzde2#

除了上面的答案之外,如果您想一次取消所有等待者,可以使用自己的计时器机制封装该行为,该机制可以取消,在After通道中发送true或false,以告诉您是从取消还是超时中唤醒所有等待者。

package main

import (
    "fmt"
    "time"
)

type CancellableTimer struct {
    cancel chan bool
}

func NewCancellableTimer() *CancellableTimer {
    return &CancellableTimer{
        cancel: make(chan bool),
    }
}

// internal wait goroutine wrapping time.After
func (c *CancellableTimer) wait(d time.Duration, ch chan bool) {
    select {
    case <-time.After(d):
        ch <- true
    case <-c.cancel:
        ch <- false
    }
}

// After mimics time.After but returns bool to signify whether we timed out or cancelled
func (c *CancellableTimer) After(d time.Duration) chan bool {
    ch := make(chan bool)
    go c.wait(d, ch)
    return ch
}

// Cancel makes all the waiters receive false
func (c *CancellableTimer) Cancel() {
    close(c.cancel)

}

// a goroutine waiting for cancellation
func B(t *CancellableTimer) {
    select {
    // timedOut will signify a timeout or cancellation
    case timedOut := <-t.After(time.Second):
        if timedOut {
            fmt.Println("Time out!")
        } else {
            fmt.Println("Cancelled!")
        }
    }
}

func main() {
    t := NewCancellableTimer()
    // Start 3 goroutines that wait for different timeouts on the same timer
    go B(t)
    go B(t)
    go B(t)

    // sleep a bit before cancelling
    time.Sleep(100 * time.Millisecond)

    // cancel the timer and all its waiters
    t.Cancel()

    // this is just to collect the output
    time.Sleep(time.Second)

}

输出:

Cancelled!
Cancelled!
Cancelled!

Playground链接:
https://play.golang.org/p/z8OscJCXTvD

t1qtbnec

t1qtbnec3#

我已经做了自己的实现,与良好的老回调和种族条件的保护:

import (
    "sync"
    "time"
)

type Timer struct {
    mutex     sync.Mutex
    timer     *time.Timer
    cancel    chan struct{}
    cancelled bool
    completed bool
}

func NewTimer(duration time.Duration, complete func()) *Timer {
    t := &Timer{}
    t.timer = time.NewTimer(duration)
    t.cancel = make(chan struct{})
    go t.wait(complete, func() {})
    return t
}

func NewTimerWithCancel(duration time.Duration, complete func(), cancel func()) *Timer {
    t := &Timer{}
    t.timer = time.NewTimer(duration)
    t.cancel = make(chan struct{})
    go t.wait(complete, cancel)
    return t
}

func (t *Timer) Cancel() {
    t.mutex.Lock()
    if t.completed {
        t.mutex.Unlock()
        return
    }
    t.cancelled = true
    t.mutex.Unlock()
    t.timer.Stop()
    t.cancel <- struct{}{}
}

func (t *Timer) wait(complete func(), cancel func()) {
    for {
        select {
        case <-t.timer.C:
            t.mutex.Lock()
            if !t.cancelled {
                t.completed = true
                t.mutex.Unlock()
                complete()
                return
            }
            t.mutex.Unlock()
        case <-t.cancel:
            cancel()
            return
        }
    }
}
func test() {
    t := NewTimerWithCancel(time.Second, func() {
        fmt.Print("Completed!")
    }, func() {
        fmt.Print("Cancelled!")
    })
    ...
    t.Cancel()
}
cig3rfwq

cig3rfwq4#

另一种不使用独立抵消信号处理停止信号的方法是在定时器通道上使用range

timer := time.NewTimer(3 * time.Second)

go func() {
    for range timer.C {
        fmt.Println("I only print if the timer fires")
    }

    fmt.Println("I print after the timer fires or if the timer is stopped")
}()

相关问题