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
希望对您有所帮助
所以我正在使用 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
希望对您有所帮助