简体   繁体   中英

Golang multiple timers with map+channel+mutex

So I'm implementing multiple timers using 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 . 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.

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). This means that sending to the channel will block until another goroutine attempts to receive from that same channel. 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.

After adding a buffer, the cancelTimer call will return immediately.

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. So we need to tell the main thread to wait for the goroutines, which sync.WaitGroup is very good for:

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. 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:

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. 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.

This all ends up looking something like https://play.golang.org/p/iQUvc52B6Nk

Hope that helps 👍

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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