这可能是菜鸟的错误。我有一个带字符串值的切片和一个通道映射。对于切片中的每个字符串,将创建一个通道并为其创建一个映射条目,并将字符串作为键。
我观看频道并将值传递给其中一个,但从未找到。
package main
import (
"fmt"
"time"
)
type TestStruct struct {
Test string
}
var channelsMap map[string](chan *TestStruct)
func main() {
stringsSlice := []string{"value1"}
channelsMap := make(map[string](chan *TestStruct))
for _, value := range stringsSlice {
channelsMap[value] = make(chan *TestStruct, 1)
go watchChannel(value)
}
<-time.After(3 * time.Second)
testStruct := new(TestStruct)
testStruct.Test = "Hello!"
channelsMap["value1"] <- testStruct
<-time.After(3 * time.Second)
fmt.Println("Program ended")
}
func watchChannel(channelMapKey string) {
fmt.Println("Watching channel: " + channelMapKey)
for channelValue := range channelsMap[channelMapKey] {
fmt.Printf("Channel '%s' used. Passed value: '%s'\n", channelMapKey, channelValue.Test)
}
}
游乐场链接:https://play.golang.org/p/IbucTqMjdGO
输出:
Watching channel: value1
Program ended
将消息馈入频道后如何执行某些操作?
答案 0 :(得分:2)
您的方法有很多问题。
第一个是您要重新声明(“屏蔽”)全局
channelsMap
函数中的变量main
。
(您是否至少完成了一些
most basic intro to Go,您应该不会遇到此类问题。)
这意味着您的watchChannel
(实际上是执行该函数的所有goroutine)读取全局channelsMap
,而您的main
函数则将其写入本地channelsMap
。 / p>
接下来发生的情况如下:
The range
statement
watchChannel
中的一个简单
映射查找表达式作为其来源-channelsMap[channelMapKey]
。
在Go中,这种形式的map lookup
永远不会失败,但是如果地图没有这样的键(或者如果地图未初始化,即为nil
),那么所谓的
"zero value"
适当类型的返回。
由于全局channelsMap
始终为空,因此对watchChannel
的任何调用都会执行映射查找,该查找始终返回
类型chan *TestStruct
的零值。
任何通道的零值为nil
。
在range
通道上执行的nil
语句
produces zero iterations。
换句话说,for
中的watchChannel
循环总是执行
零次。
更复杂的问题仍然不是全局变量的影子,而是goroutine之间完全没有同步。您正在使用“睡眠”作为一种创可贴,试图在goroutine之间执行隐式同步 但是尽管这似乎可以通过所谓的 “常识”,两个人在实践中都行不通 原因:
存在各种在goroutine之间同步执行的方法。基本上,它们等于使用sync
包提供的类型通过通道发送和接收。
在您的特定情况下,最简单的方法可能是使用sync.WaitGroup
类型。
Here是我们要做的
解决上述问题后,请执行以下操作:
-在地图变量的位置对其进行初始化
定义,不要在main
中混淆它。
-使用sync.WaitGroup
使main
正确等待所有
它产生的goroutines完成了:
package main
import (
"fmt"
"sync"
)
type TestStruct struct {
Test string
}
var channelsMap = make(map[string](chan *TestStruct))
func main() {
stringsSlice := []string{"value1"}
var wg sync.WaitGroup
wg.Add(len(stringsSlice))
for _, value := range stringsSlice {
channelsMap[value] = make(chan *TestStruct, 1)
go watchChannel(value, &wg)
}
testStruct := new(TestStruct)
testStruct.Test = "Hello!"
channelsMap["value1"] <- testStruct
wg.Wait()
fmt.Println("Program ended")
}
func watchChannel(channelMapKey string, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Watching channel: " + channelMapKey)
for channelValue := range channelsMap[channelMapKey] {
fmt.Printf("Channel '%s' used. Passed value: '%s'\n", channelMapKey, channelValue.Test)
}
}
一旦我们将解决您的代码的接下来的两个问题
解决了前两个问题-在您制作了“观察者” goroutine之后
使用与运行main
的goroutine相同的map变量,并且
让后者适当地等待观察者:
有一个data race
在地图变量之间
在for
循环产生后更新地图的代码
观察者goroutine结束,访问此例程的代码
所有观察者goroutine中的变量。
有一个deadlock 在观察者goroutine和等待他们完成的主要goroutine之间。
僵局的原因是观察者goroutines 永远不会收到任何必须退出处理的信号, 因此永远被困在试图从各自的阅读中 渠道。
解决这两个新问题的方法很简单,但是它们 可能实际上会“破坏”您最初的结构构想 您的代码。
首先,我将通过简单地让观察者来消除数据争用
不访问map变量。如您所见,每次调用
watchChannel
收到一个单一值,用作
从共享地图中读取值,因此每个观察者始终
在其运行时仅读取一次单个值。
如果我们删除多余的代码,代码将变得更加清晰
完全访问地图,而是通过相应的频道
直接给每个观察者带来价值。
一个不错的副产品是我们不需要全球
地图变量了。
Here是我们将会得到的:
package main
import (
"fmt"
"sync"
)
type TestStruct struct {
Test string
}
func main() {
stringsSlice := []string{"value1"}
channelsMap := make(map[string](chan *TestStruct))
var wg sync.WaitGroup
wg.Add(len(stringsSlice))
for _, value := range stringsSlice {
channelsMap[value] = make(chan *TestStruct, 1)
go watchChannel(value, channelsMap[value], &wg)
}
testStruct := new(TestStruct)
testStruct.Test = "Hello!"
channelsMap["value1"] <- testStruct
wg.Wait()
fmt.Println("Program ended")
}
func watchChannel(channelMapKey string, ch <-chan *TestStruct, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Watching channel: " + channelMapKey)
for channelValue := range ch {
fmt.Printf("Channel '%s' used. Passed value: '%s'\n", channelMapKey, channelValue.Test)
}
}
好的,我们仍然有僵局。
有多种方法可以解决此问题,但这取决于
根据实际情况,并通过此玩具示例,
尝试迭代至少其中一部分
弄乱水。
相反,在这种情况下,我们采用最简单的方法:关闭
通道立即对其进行任何挂起的接收操作
解除阻塞并产生通道类型的零值。
对于使用range
语句进行迭代的频道
它只是意味着结局终止而没有产生任何
渠道中的价值。
换句话说,让我们关闭所有通道以解除阻止
观察者goroutine正在运行的range
语句
然后等待这些goroutine通过wait组报告其完成情况。
为了避免答案太长,我还添加了字符串切片的程序初始化,通过让多个观察者(而不仅仅是一个观察者)实际起作用,来使示例更加有趣:
package main
import (
"fmt"
"sync"
)
type TestStruct struct {
Test string
}
func main() {
var stringsSlice []string
channelsMap := make(map[string](chan *TestStruct))
for i := 1; i <= 10; i++ {
stringsSlice = append(stringsSlice, fmt.Sprintf("value%d", i))
}
var wg sync.WaitGroup
wg.Add(len(stringsSlice))
for _, value := range stringsSlice {
channelsMap[value] = make(chan *TestStruct, 1)
go watchChannel(value, channelsMap[value], &wg)
}
for _, value := range stringsSlice {
testStruct := new(TestStruct)
testStruct.Test = fmt.Sprint("Hello! ", value)
channelsMap[value] <- testStruct
}
for _, ch := range channelsMap {
close(ch)
}
wg.Wait()
fmt.Println("Program ended")
}
func watchChannel(channelMapKey string, ch <-chan *TestStruct, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("Watching channel: " + channelMapKey)
for channelValue := range ch {
fmt.Printf("Channel '%s' used. Passed value: '%s'\n", channelMapKey, channelValue.Test)
}
}
如您所见,您应该实际学习一些东西 在着手与 并发。
我建议按以下顺序进行操作:
sync
包中的通道和类型来解决并发问题的简要介绍。答案 1 :(得分:0)
上面提到的channelsMap主窗口中的阴影是一个严重的错误,但是除此之外,该程序还在播放带有时间调用的“俄罗斯轮盘”。之后,main不会在观察者goroutine之前完成。这是不稳定且不可靠的,因此我建议使用以下方法使用通道来通知所有观察程序goroutine完成时:
package main
import (
"fmt"
)
type TestStruct struct {
Test string
}
var channelsMap map[string](chan *TestStruct)
func main() {
stringsSlice := []string{"value1", "value2", "value3"}
structsSlice := []TestStruct{
{"Hello1"},
{"Hello2"},
{"Hello3"},
}
channelsMap = make(map[string](chan *TestStruct))
// Signal channel to wait for watcher goroutines.
done := make(chan struct{})
for _, s := range stringsSlice {
channelsMap[s] = make(chan *TestStruct)
// Give watcher goroutines the signal channel.
go watchChannel(s, done)
}
for _, ts := range structsSlice {
for _, s := range stringsSlice {
channelsMap[s] <- &ts
}
}
// Close the channels so watcher goroutines can finish.
for _, s := range stringsSlice {
close(channelsMap[s])
}
// Wait for all watcher goroutines to finish.
for range stringsSlice {
<-done
}
// Now we're really done!
fmt.Println("Program ended")
}
func watchChannel(channelMapKey string, done chan<- struct{}) {
fmt.Println("Watching channel: " + channelMapKey)
for channelValue := range channelsMap[channelMapKey] {
fmt.Printf("Channel '%s' used. Passed value: '%s'\n", channelMapKey, channelValue.Test)
}
done <- struct{}{}
}
(转到Playground链接:https://play.golang.org/p/eP57Ru44-NW)
重要的是使用完成的通道让观察者goroutine发出信号,表明它们已完成维护。另一个关键部分是一旦您完成频道的关闭。如果不关闭它们,观察器goroutine中的范围循环将永远不会结束,永远等待。关闭通道后,范围循环将退出,观察者goruoutine可以在已完成的通道上发送信号,表明它已完成工作。
最后,回到main,您必须为创建的每个观察者goroutine在完成频道上接收一次。由于观察者goroutine的数量等于stringsSlice中的项目数量,因此您只需在stringsSlice范围内调整,即可从done通道接收正确的时间。完成此操作后,可以退出main函数,并确保所有观察者都已完成。