无法获取工作渠道图

时间:2018-08-26 10:29:34

标签: go slice channel

这可能是菜鸟的错误。我有一个带字符串值的切片和一个通道映射。对于切片中的每个字符串,将创建一个通道并为其创建一个映射条目,并将字符串作为键。

我观看频道并将值传递给其中一个,但从未找到。

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

将消息馈入频道后如何执行某些操作?

2 个答案:

答案 0 :(得分:2)

您的方法有很多问题。

第一个是您要重新声明(“屏蔽”)全局 channelsMap函数中的变量main。 (您是否至少完成了一些 most basic intro to Go,您应该不会遇到此类问题。)

这意味着您的watchChannel(实际上是执行该函数的所有goroutine)读取全局channelsMap,而您的main函数则将其写入本地channelsMap。 / p>

接下来发生的情况如下:

  1. The range statement watchChannel中的一个简单 映射查找表达式作为其来源-channelsMap[channelMapKey]

    在Go中,这种形式的map lookup 永远不会失败,但是如果地图没有这样的键(或者如果地图未初始化,即为nil),那么所谓的 "zero value" 适当类型的返回。

  2. 由于全局channelsMap始终为空,因此对watchChannel的任何调用都会执行映射查找,该查找始终返回 类型chan *TestStruct的零值。 任何通道的零值为nil

  3. range通道上执行的nil语句 produces zero iterations。 换句话说,for中的watchChannel循环总是执行 零次。

更复杂的问题仍然不是全局变量的影子,而是goroutine之间完全没有同步。您正在使用“睡眠”作为一种创可贴,试图在goroutine之间执行隐式同步 但是尽管这似乎可以通过所谓的 “常识”,两个人在实践中都行不通 原因:

  • 休眠始终是一种幼稚的同步方法,因为它仅表明所有goroutine将相对自由地运行且不受竞争的事实。在许多(如果不是大多数)生产设置中,事实并非如此,因此,这始终是细微错误的原因。请不要再这样做。
  • Go memory model中没有任何内容 说,在运行时将等待壁钟时间视为建立不同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)
    }

}

Playground link


如您所见,您应该实际学习一些东西 在着手与 并发。

我建议按以下顺序进行操作:

  1. The Go tour会让您习惯于并发。
  2. The Go Programming Language共有两章,专门为读者提供使用sync包中的通道和类型来解决并发问题的简要介绍。
  3. Concurrency In Go继续介绍了人们如何在Go中处理并发性的更多核心细节,包括解决并发程序在生产中面临的现实问题的高级主题,例如对传入请求进行速率限制的方法。

答案 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函数,并确保所有观察者都已完成。