简体   繁体   English

Golang具有map + channel + mutex的多个计时器

[英]Golang multiple timers with map+channel+mutex

So I'm implementing multiple timers using map/channel/mutex. 所以我正在使用map / channel / mutex实现多个计时器。 In order for timer to cancel, I have a channel map that stores cancel info, below is the code: 为了取消计时器,我有一个存储取消信息的通道图,下面是代码:

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))
        }
    }
}

Now this one gives me deadlock , if I remove all mutex.lock/unlock, it gives me concurrent map read and map write . 现在,这给了我deadlock ,如果我删除所有的mutex.lock / unlock,它将给我concurrent map read and map write So what am I doing wrong? 那我在做什么错?

I know sync.Map solves my problem, but the performance suffers significantly, so I kinda wanna stick with the map solution. 我知道sync.Map解决了我的问题,但是性能却受到很大影响,因此我想坚持使用地图解决方案。

Thanks in advance! 提前致谢!

There's a few things going on here which are going to cause problems with your script: 这里发生了一些会导致脚本问题的事情:

cancelTimer creates a channel make(chan interface{}) which has no buffer, eg make(chan struct{}, 1). cancelTimer创建一个没有缓冲区的通道make(chan interface {}),例如make(chan struct {},1)。 This means that sending to the channel will block until another goroutine attempts to receive from that same channel. 这意味着向该通道的发送将被阻塞,直到另一个goroutine尝试从该通道接收。 So when you attempt to call cancelTimer from the main goroutine, it locks mutexLocker and then blocks on sending the cancellation, meanwhile no other goroutine can lock mutexLocker to receive from the cancellation channel, thus causing a deadlock. 因此,当您尝试从主goroutine调用cancelTimer时,它将锁定MutexLocker,然后在发送取消消息时阻止,与此同时,其他goroutine无法锁定MutexLocker从取消通道接收消息,从而导致死锁。

After adding a buffer, the cancelTimer call will return immediately. 添加缓冲区后,cancelTimer调用将立即返回。

We will then run into a few other little issues. 然后,我们将遇到其他一些小问题。 The first is that the program will immediately quit without printing anything. 首先是程序将立即退出而不打印任何内容。 This happens because after launching the test goroutines and sending the cancel, the main thread has done all of its work, which tells the program it is finished. 发生这种情况是因为在启动测试goroutine并发送了cancel之后,主线程已经完成了所有工作,这告诉程序已完成。 So we need to tell the main thread to wait for the goroutines, which sync.WaitGroup is very good for: 因此,我们需要告诉主线程等待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()
}

I can see you've added the mutexLocker to protect the map and later added the for loop to give each goroutine an opportunity to acquire mutexLocker to check their timers. 我可以看到您已经添加了MutexLocker来保护地图,后来又添加了for循环,从而为每个goroutine提供了获取MutexLocker来检查其计时器的机会。 This results in a lot of work for the computer, and more complicated code than is necessary. 这为计算机带来了很多工作,并使代码变得比必需的更为复杂。 Instead of having timerStart look up it's index in the cancellations map, we can provide the cancellation channel as an argument: 我们可以提供取消渠道作为参数,而不是让timerStart在取消映射中查找它的索引:

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

and have the main function create the channels. 并具有创建频道的主要功能。 You will then be a le to remove map access, mutexLocker locking, and the for loop from testTimer. 然后,您将可以从testTimer中删除映射访问,mutexLocker锁定和for循环。 If you still require the map for purposes not shown here, you can put the same channel in the map that you pass to testTimer, and if not you can remove all of that code too. 如果出于此处未显示的目的仍需要映射,则可以在传递给testTimer的映射中放置相同的通道,如果没有,则也可以删除所有该代码。

This all ends up looking something like https://play.golang.org/p/iQUvc52B6Nk 这一切最终看起来像https://play.golang.org/p/iQUvc52B6Nk

Hope that helps 👍 希望有帮助helps

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

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