Golang 多个计时器与 map+channel+mutex

Golang multiple timers with map+channel+mutex

所以我正在使用 map/channel/mutex 实现多个计时器。为了取消计时器,我有一个存储取消信息的频道映射,下面是代码:

var timerCancelMap = make(map[string]chan interface{})
var mutexLocker sync.Mutex

func cancelTimer(timerIndex string) {
    mutexLocker.Lock()
    defer mutexLocker.Unlock()
    timerCancelMap[timerIndex] = make(chan interface{})
    timerCancelMap[timerIndex] <- struct{}{}
}

func timerStart(timerIndex string) {
    fmt.Println("###### 1. start timer: ", timerIndex)
    timerStillActive := true
    newTimer := time.NewTimer(time.Second * 10)
    for timerStillActive {
        mutexLocker.Lock()
        select {
        case <-newTimer.C:
            timerStillActive = false
            fmt.Println("OOOOOOOOO timer time's up: ", timerIndex)
        case <-timerCancelMap[timerIndex]:
            timerCancelMap[timerIndex] = nil
            timerStillActive = false
            fmt.Println("XXXXXXXXX timer canceled: ", timerIndex)
        default:
        }
        mutexLocker.Unlock()
    }
    fmt.Println("###### 2. end timer: ", timerIndex)
}

func main() {
    for i := 0; i < 10; i++ {
        go timerStart(strconv.Itoa(i))
        if i%10 == 0 {
            cancelTimer(strconv.Itoa(i))
        }
    }
}

现在这个给了我 deadlock,如果我删除所有互斥量。lock/unlock,它给了我 concurrent map read and map write。那我做错了什么?

我知道 sync.Map 解决了我的问题,但性能受到很大影响,所以我有点想坚持使用地图解决方案。

提前致谢!

此处发生的一些事情会导致您的脚本出现问题:

cancelTimer 创建一个没有缓冲区的通道 make(chan interface{}),例如制作(陈结构{},1)。这意味着发送到通道将阻塞,直到另一个 goroutine 尝试从同一通道接收。因此,当您尝试从主 goroutine 调用 cancelTimer 时,它会锁定 mutexLocker,然后阻止发送取消,同时没有其他 goroutine 可以锁定 mutexLocker 以从取消通道接收,从而导致死锁。

添加缓冲区后,cancelTimer 调用将立即return。

然后我们将 运行 讨论其他一些小问题。首先是程序将立即退出而不打印任何内容。发生这种情况是因为在启动测试 goroutine 并发送取消后,主线程已完成所有工作,这告诉程序它已完成。所以我们需要告诉主线程等待 goroutines,sync.WaitGroup 非常适合:

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            timerStart(strconv.Itoa(i))
        }(i)
        if i%10 == 0 {
            cancelTimer(strconv.Itoa(i))
        }
    }
    wg.Wait()
}

我看到你已经添加了 mutexLocker 来保护地图,后来又添加了 for 循环,让每个 goroutine 都有机会获取 mutexLocker 来检查它们的计时器。这导致计算机需要做大量工作,并且代码比必要的更复杂。我们可以提供取消通道作为参数,而不是让 timerStart 在取消映射中查找它的索引:

func testTimer(i int, cancel <-chan interface{}) {

并让主函数创建频道。然后您将成为一个文件,从 testTimer 中删除映射访问、mutexLocker 锁定和 for 循环。如果您仍然需要地图用于此处未显示的用途,您可以将相同的通道放在传递给 testTimer 的地图中,如果不需要,您也可以删除所有代码。

这一切最终看起来像 https://play.golang.org/p/iQUvc52B6Nk

希望对您有所帮助