繁体   English   中英

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 ,如果我删除所有的mutex.lock / unlock,它将给我concurrent map read and map write 那我在做什么错?

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

提前致谢!

这里发生了一些会导致脚本问题的事情:

cancelTimer创建一个没有缓冲区的通道make(chan interface {}),例如make(chan struct {},1)。 这意味着向该通道的发送将被阻塞,直到另一个goroutine尝试从该通道接收。 因此,当您尝试从主goroutine调用cancelTimer时,它将锁定MutexLocker,然后在发送取消消息时阻止,与此同时,其他goroutine无法锁定MutexLocker从取消通道接收消息,从而导致死锁。

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

然后,我们将遇到其他一些小问题。 首先是程序将立即退出而不打印任何内容。 发生这种情况是因为在启动测试goroutine并发送了cancel之后,主线程已经完成了所有工作,这告诉程序已完成。 因此,我们需要告诉主线程等待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

希望有帮助helps

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM