Не могу заставить карту каналов работать - PullRequest
0 голосов
/ 26 августа 2018

Это может быть ошибкой новичков. У меня есть фрагмент со строковым значением и карта каналов. Для каждой строки в срезе создается канал и для него создается запись карты со строкой в ​​качестве ключа.

Я смотрю каналы и передаю значение одному из них, которое никогда не найдено.

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 голосов
/ 26 августа 2018

Есть много проблем с вашим подходом.

Первая - это то, что вы переделываете («затенение») глобальную переменную channelsMap в вашей функции main.(Если бы вы выполнили хотя бы несколько самых основных вступлений к Go , у вас не было бы такой проблемы.)

Это означает, что ваши watchChannel (на самом деле, все goroutines, которые выполняют этофункция) читает глобальный channelsMap, в то время как ваша main функция записывает в локальный channelsMap.

Что происходит дальше, выглядит следующим образом:

  1. Оператор range в watchChannel имеет простое выражение поиска по карте в качестве своего источника - channelsMap[channelMapKey].

    В Go эта форма поиска по карте никогда не выходит из строя, , но если карта не имеет такого ключа (или если карта не инициализирована, то есть nil), так называемое «нулевое значение» соответствующеготип возвращается.

  2. Поскольку глобальный channelsMap всегда пуст, любой вызов watchChannel выполняет поиск карты, который всегда возвращает нулевое значение типа chan *TestStruct.Нулевое значение для любого канала равно nil.

  3. Оператор range, выполненный по каналу nil , производит нулевые итерации .Другими словами, цикл for в watchChannel всегда выполняется ноль раз.

Тем не менее, более сложной проблемой является не дублирование глобальной переменной, а полное отсутствиесинхронизация между программами.Вы используете «сон» как своего рода лейкопластырь в попытке выполнить неявную синхронизацию между программами, но, хотя, судя по так называемому «здравому смыслу», это нормально, это не такработать на практике по двум причинам:

  • Спящий режим всегда наивный подход к синхронизации, поскольку он зависит исключительно от того факта, что все программы будут выполняться относительно свободно и без участия.Это далеко не так во многих (если не в большинстве) производственных настройках и, следовательно, всегда является причиной незначительных ошибок.Больше никогда не делайте этого, пожалуйста.
  • Ничто в модели памяти Go не говорит о том, что ожидание относительно времени настенных часов рассматривается средой выполнения как установление порядка выполнения различных операций.Goroutines связаны друг с другом.

Существуют различные способы синхронизации выполнения между Goroutines.В основном они равны количеству отправляемых и получаемых по каналам и использующих типы, предоставляемые пакетом sync.В вашем конкретном случае самый простой подход, вероятно, использует тип sync.WaitGroup.

Здесь - это то, что мы получили бы после устранения проблем, описанных выше: - Инициализируйте переменную map прямо в точкеего определения и не связываться с ним в 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)
    }

}

Следующие две проблемы с вашим кодом станут очевидными, как только мы получимисправлены первые два - после того, как вы запрограммировали «наблюдатели», используйте ту же переменную карты, что и подпрограмма, запускающая main, и заставьте последнюю должным образом ожидать наблюдателей:

  • Есть гонка данных по переменной карты между кодом, который обновляет карту после завершения цикла for, порождающего программы-наблюдатели, и кодом, который обращается к этой переменной во всех программах-наблюдателях.

  • Существует тупик между программами-наблюдателями и главной программой, которая ожидает их завершения.

    Причина тупика заключается в том, что программы-наблюдатели никогда не получают никакихсигнал, что они должны выйти из обработки и, следовательно, застряли навсегда, пытаясь прочитать с их соответствующих каналов.

ПутиУстранить эти две новые проблемы просто, но на самом деле они могут «сломать» вашу первоначальную идею структурирования кода.

Во-первых, я бы убрал гонку данных, просто заставив наблюдателей не обращаться к карте.переменная. Как видите, каждый вызов watchChannel получает одно значение для использования в качестве ключа читать значение из общей карты, и, следовательно, каждый наблюдатель всегда читает одно значение ровно один раз за время выполнения. Код станет намного понятнее, если мы удалим этот дополнительный доступ к карте в целом и вместо этого передать соответствующий канал значение непосредственно для каждого наблюдателя. Хорошим побочным продуктом этого является то, что нам не нужно глобальное переменная карты больше.

Вот вот что мы получим:

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 это просто означает, что действие прекращается без какого-либо значение из канала.

Другими словами, давайте просто закроем все каналы, чтобы разблокировать range операторы, выполняемые наблюдателями а затем дождитесь, пока эти программы запишут их выполнение через группу ожидания.

Чтобы не делать ответ слишком длинным, я также добавил программную инициализацию фрагмента строки, чтобы сделать пример более интересным, имея несколько наблюдателей, а не только одного, на самом деле выполняющих полезную работу:

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

}

Ссылка на игровую площадку .


Как вы можете видеть, есть вещи, которые вы должны на самом деле выучить более подробно, прежде чем приступить к работе с параллелизм.

Я бы рекомендовал действовать в следующем порядке:

  1. Тур по Го позволит вам привыкнуть к голым костям параллелизма.
  2. Язык программирования Go состоит из двух глав, посвященных ознакомлению читателей с вопросами параллелизма как с использованием каналов, так и типов из пакета sync.
  3. Параллелизм в Go продолжается с представлением более сложных деталей о том, как работать с параллелизмом в Go, включая сложные темы, касающиеся реальных проблем, с которыми сталкиваются параллельные программы в производстве, такие как способы оценки -лимитировать входящие запросы.
0 голосов
/ 26 августа 2018

Затенение в main of channelMap, упомянутое выше, было критической ошибкой, но, кроме этого, программа играла в «русскую рулетку» с вызовами времени. После этого main не заканчивал раньше, чем выполняли наблюдатели. Это нестабильно и ненадежно, поэтому я рекомендую следующий подход, использующий канал, чтобы сигнализировать о завершении всех процедур-наблюдателей:

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

(ссылка на игровую площадку Go: https://play.golang.org/p/eP57Ru44-NW)

Важным является использование готового канала, чтобы позволить программам-наблюдателям сигнализировать о том, что они закончили с основным. Другая важная часть - закрытие каналов, когда вы закончите с ними. Если вы не закроете их, циклы диапазона в наблюдателях никогда не закончатся, ожидая вечно. Как только вы закроете канал, петля диапазона выйдет, и программа-наблюдатель сможет отправить по готовому каналу, сигнализируя, что он завершил работу.

Наконец, вернувшись в главное, вы должны получать по готовому каналу один раз за каждую созданную вами программу-наблюдатель. Так как количество процедур-наблюдателей равно количеству элементов в stringsSlice, вы просто изменяете диапазон по stringsSlice, чтобы получить правильное количество раз из готового канала. После завершения основная функция может выйти с гарантией того, что все наблюдатели завершили работу.

...