Как поделиться картой через горутины

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

уведомление должно срабатывать только один раз, когда первая выборка падает ниже порогового значения, а последующие выборки ниже этого значения не должны срабатывать снова, ПОКА значение не поднимется выше порога.

например, скажем, мой порог равен 10, и я отправляю выборки из 15, 14, 11, 10, ... 9. после отправки 9 должно запускаться уведомление. дальнейшие пробы 8, 7, 4 не должны вызывать никакого эффекта. следующие образцы, такие как 5, 6, 7, 9, 10, 11, 14, 30, не должны ничего делать. как только образец снова упадет ниже 10:30, 20, 15, 10, 7 ... необходимо отправить другое уведомление.

У меня проблема, когда несколько горутин манипулируют моей структурой.

Я пробовал синхронизировать с помощью sync.Mutex и ТАКЖЕ использовать sync.Map, но безуспешно. Я чувствую, что где-то есть справочная копия или кеширование, но я слишком новичок в Go, чтобы найти проблему.

для этого я создал такую ​​структуру:

type Notifier interface {
    Send(message string)
}

type NotificationBoard struct {
    mutex    sync.Mutex
    Last     sync.Map
    notifier Notifier
}

func (n *NotificationBoard) Init(notifier Notifier) {
    n.notifier = notifier
}

// NotifyLess ...
func (n *NotificationBoard) NotifyLess(key string, value, threshold float64) {
    n.mutex.Lock()
    defer n.mutex.Unlock()

    if value >= threshold {
        fmt.Printf("NotificationBoard.NotifyLess %v (value >= threshold): %v >= %v\n", key, value, threshold)
        n.Last.Store(key, value)
        return
    }

    // value < threshold
    if last, found := n.Last.Load(key); found == true {
        fmt.Printf("NotificationBoard.NotifyLess %v (value < threshold): %v < %v : found %v\n", key, value, threshold, last)
        if last.(float64) >= threshold { // first trigger
            n.notifier.Send(fmt.Sprintf("%s < %v (%v)", key, threshold, value))
        }
    } else {
        fmt.Printf("NotificationBoard.NotifyLess %v (value < threshold): %v < %v : not found\n", key, value, threshold)
        // not found, started board as less
        n.notifier.Send(fmt.Sprintf("%s < %v (%v)", key, threshold, value))
    }

    n.Last.Store(key, value)
    return
}

Я знаю, что этого должно быть достаточно, используя sync.Mutex ИЛИ sync.Map, но в приведенном выше коде есть и то, и другое, потому что это моя текущая (сломанная) версия.

для тестирования я установил следующий код:

type dummy struct{}

func (d *dummy) Send(message string) {
    fmt.Println("--------------> notifying", message)
}

func newBoard() *NotificationBoard {
    notificationBoard := &NotificationBoard{}
    notificationBoard.Init(&dummy{})
    return notificationBoard
}

Я также добавил несколько трассировок fmt.Println (не включенных в мой код выше для краткости) и сначала подготовил тест с одним гуру (который работает, как ожидалось):

func Test1(t *testing.T) {
    board := newBoard()
    board.NotifyLess("k1", 15, 10)
    board.NotifyLess("k1", 10, 10)
    board.NotifyLess("k1", 5, 10)
    board.NotifyLess("k1", 4, 10)
    board.NotifyLess("k1", 3, 10)
    board.NotifyLess("k1", 10, 10)
    board.NotifyLess("k1", 15, 10)
    board.NotifyLess("k1", 20, 10)
    board.NotifyLess("k1", 15, 10)
    board.NotifyLess("k1", 10, 10)
    board.NotifyLess("k1", 5, 10)
    board.NotifyLess("k1", 1, 10)
}

выходы:

> go test -run Test1
NotificationBoard.NotifyLess k1 (value >= threshold): 15 >= 10
NotificationBoard.NotifyLess k1 (value >= threshold): 10 >= 10
NotificationBoard.NotifyLess k1 (value < threshold): 5 < 10 : found 10
--------------> notifying k1 < 10 (5)
NotificationBoard.NotifyLess k1 (value < threshold): 4 < 10 : found 5
NotificationBoard.NotifyLess k1 (value < threshold): 3 < 10 : found 4
NotificationBoard.NotifyLess k1 (value >= threshold): 10 >= 10
NotificationBoard.NotifyLess k1 (value >= threshold): 15 >= 10
NotificationBoard.NotifyLess k1 (value >= threshold): 20 >= 10
NotificationBoard.NotifyLess k1 (value >= threshold): 15 >= 10
NotificationBoard.NotifyLess k1 (value >= threshold): 10 >= 10
NotificationBoard.NotifyLess k1 (value < threshold): 5 < 10 : found 10
--------------> notifying k1 < 10 (5)
NotificationBoard.NotifyLess k1 (value < threshold): 1 < 10 : found 5
PASS

мы видим, что вывод «notifying ....» происходит дважды, только в моменты, когда выборка опускается ниже порогового значения.

но затем я создал тест с несколькими гоурутинами, и затем уведомления появляются несколько раз:

func Test3(t *testing.T) {
    preparing := sync.WaitGroup{}
    preparing.Add(1)

    board := newBoard()
    wg := sync.WaitGroup{}

    for i := 0; i < 30; i++ {
        wg.Add(1)
        go func(x int, not *NotificationBoard) {
            fmt.Printf("routine %v waiting preparation... \n", x)
            preparing.Wait()

            for j := 15.0; j > 5; j-- {
                fmt.Printf("routine %v notifying %v\n", x, j)
                not.NotifyLess("keyX", j+float64(x+1)/100, 10)
            }

            wg.Done()
        }(i, board)
    }

    preparing.Done()
    wg.Wait()

}

который выводит:

> go test -run Test3
routine 7 waiting preparation...
routine 2 waiting preparation...
routine 2 notifying 15
NotificationBoard.NotifyLess keyX (value >= threshold): 15.03 >= 10
routine 2 notifying 14
NotificationBoard.NotifyLess keyX (value >= threshold): 14.03 >= 10
routine 2 notifying 13
NotificationBoard.NotifyLess keyX (value >= threshold): 13.03 >= 10
routine 2 notifying 12
NotificationBoard.NotifyLess keyX (value >= threshold): 12.03 >= 10
routine 2 notifying 11
NotificationBoard.NotifyLess keyX (value >= threshold): 11.03 >= 10
routine 2 notifying 10
NotificationBoard.NotifyLess keyX (value >= threshold): 10.03 >= 10
routine 2 notifying 9
NotificationBoard.NotifyLess keyX (value < threshold): 9.03 < 10 : found 10.03
--------------> notifying keyX < 10 (9.03)
routine 2 notifying 8
NotificationBoard.NotifyLess keyX (value < threshold): 8.03 < 10 : found 9.03
routine 2 notifying 7
NotificationBoard.NotifyLess keyX (value < threshold): 7.03 < 10 : found 8.03
routine 2 notifying 6
NotificationBoard.NotifyLess keyX (value < threshold): 6.03 < 10 : found 7.03
routine 14 waiting preparation...
routine 14 notifying 15
NotificationBoard.NotifyLess keyX (value >= threshold): 15.15 >= 10
routine 14 notifying 14
NotificationBoard.NotifyLess keyX (value >= threshold): 14.15 >= 10
routine 14 notifying 13
NotificationBoard.NotifyLess keyX (value >= threshold): 13.15 >= 10
routine 14 notifying 12
NotificationBoard.NotifyLess keyX (value >= threshold): 12.15 >= 10
routine 14 notifying 11
NotificationBoard.NotifyLess keyX (value >= threshold): 11.15 >= 10
routine 14 notifying 10
NotificationBoard.NotifyLess keyX (value >= threshold): 10.15 >= 10
routine 14 notifying 9
NotificationBoard.NotifyLess keyX (value < threshold): 9.15 < 10 : found 10.15
--------------> notifying keyX < 10 (9.15)
routine 14 notifying 8
NotificationBoard.NotifyLess keyX (value < threshold): 8.15 < 10 : found 9.15
routine 14 notifying 7
NotificationBoard.NotifyLess keyX (value < threshold): 7.15 < 10 : found 8.15
routine 14 notifying 6
NotificationBoard.NotifyLess keyX (value < threshold): 6.15 < 10 : found 7.15
routine 22 waiting preparation...
routine 27 waiting preparation...
routine 27 notifying 15
NotificationBoard.NotifyLess keyX (value >= threshold): 15.28 >= 10
routine 27 notifying 14
NotificationBoard.NotifyLess keyX (value >= threshold): 14.28 >= 10
routine 27 notifying 13
NotificationBoard.NotifyLess keyX (value >= threshold): 13.28 >= 10
routine 27 notifying 12
NotificationBoard.NotifyLess keyX (value >= threshold): 12.28 >= 10
routine 27 notifying 11
NotificationBoard.NotifyLess keyX (value >= threshold): 11.28 >= 10
routine 27 notifying 10
NotificationBoard.NotifyLess keyX (value >= threshold): 10.28 >= 10
routine 27 notifying 9
NotificationBoard.NotifyLess keyX (value < threshold): 9.28 < 10 : found 10.28
--------------> notifying keyX < 10 (9.28)
routine 27 notifying 8
NotificationBoard.NotifyLess keyX (value < threshold): 8.28 < 10 : found 9.28
routine 27 notifying 7
NotificationBoard.NotifyLess keyX (value < threshold): 7.28 < 10 : found 8.28
routine 27 notifying 6
NotificationBoard.NotifyLess keyX (value < threshold): 6.28 < 10 : found 7.28
routine 20 waiting preparation...
routine 20 notifying 15
NotificationBoard.NotifyLess keyX (value >= threshold): 15.21 >= 10
routine 20 notifying 14
NotificationBoard.NotifyLess keyX (value >= threshold): 14.21 >= 10
routine 20 notifying 13
NotificationBoard.NotifyLess keyX (value >= threshold): 13.21 >= 10
routine 20 notifying 12
NotificationBoard.NotifyLess keyX (value >= threshold): 12.21 >= 10
routine 20 notifying 11
NotificationBoard.NotifyLess keyX (value >= threshold): 11.21 >= 10
routine 20 notifying 10
NotificationBoard.NotifyLess keyX (value >= threshold): 10.21 >= 10
routine 20 notifying 9
NotificationBoard.NotifyLess keyX (value < threshold): 9.21 < 10 : found 10.21
--------------> notifying keyX < 10 (9.21)
routine 20 notifying 8
NotificationBoard.NotifyLess keyX (value < threshold): 8.21 < 10 : found 9.21
routine 20 notifying 7
NotificationBoard.NotifyLess keyX (value < threshold): 7.21 < 10 : found 8.21
routine 20 notifying 6
NotificationBoard.NotifyLess keyX (value < threshold): 6.21 < 10 : found 7.21
routine 19 waiting preparation...
routine 19 notifying 15
NotificationBoard.NotifyLess keyX (value >= threshold): 15.2 >= 10
routine 19 notifying 14
NotificationBoard.NotifyLess keyX (value >= threshold): 14.2 >= 10
routine 19 notifying 13
NotificationBoard.NotifyLess keyX (value >= threshold): 13.2 >= 10
routine 19 notifying 12
NotificationBoard.NotifyLess keyX (value >= threshold): 12.2 >= 10
routine 19 notifying 11
NotificationBoard.NotifyLess keyX (value >= threshold): 11.2 >= 10
routine 19 notifying 10
NotificationBoard.NotifyLess keyX (value >= threshold): 10.2 >= 10
routine 19 notifying 9
NotificationBoard.NotifyLess keyX (value < threshold): 9.2 < 10 : found 10.2
--------------> notifying keyX < 10 (9.2)
routine 19 notifying 8
NotificationBoard.NotifyLess keyX (value < threshold): 8.2 < 10 : found 9.2
routine 19 notifying 7
NotificationBoard.NotifyLess keyX (value < threshold): 7.2 < 10 : found 8.2
routine 19 notifying 6
NotificationBoard.NotifyLess keyX (value < threshold): 6.2 < 10 : found 7.2
routine 0 waiting preparation...
routine 0 notifying 15
NotificationBoard.NotifyLess keyX (value >= threshold): 15.01 >= 10
routine 0 notifying 14
NotificationBoard.NotifyLess keyX (value >= threshold): 14.01 >= 10
routine 0 notifying 13
NotificationBoard.NotifyLess keyX (value >= threshold): 13.01 >= 10
routine 0 notifying 12
NotificationBoard.NotifyLess keyX (value >= threshold): 12.01 >= 10
routine 0 notifying 11
NotificationBoard.NotifyLess keyX (value >= threshold): 11.01 >= 10
routine 0 notifying 10
NotificationBoard.NotifyLess keyX (value >= threshold): 10.01 >= 10
routine 0 notifying 9
NotificationBoard.NotifyLess keyX (value < threshold): 9.01 < 10 : found 10.01
--------------> notifying keyX < 10 (9.01)
routine 0 notifying 8
NotificationBoard.NotifyLess keyX (value < threshold): 8.01 < 10 : found 9.01
routine 0 notifying 7
NotificationBoard.NotifyLess keyX (value < threshold): 7.01 < 10 : found 8.01
routine 0 notifying 6
NotificationBoard.NotifyLess keyX (value < threshold): 6.01 < 10 : found 7.01
routine 17 waiting preparation...
routine 17 notifying 15
NotificationBoard.NotifyLess keyX (value >= threshold): 15.18 >= 10
routine 17 notifying 14
NotificationBoard.NotifyLess keyX (value >= threshold): 14.18 >= 10
routine 17 notifying 13
NotificationBoard.NotifyLess keyX (value >= threshold): 13.18 >= 10
routine 17 notifying 12
NotificationBoard.NotifyLess keyX (value >= threshold): 12.18 >= 10
routine 17 notifying 11
NotificationBoard.NotifyLess keyX (value >= threshold): 11.18 >= 10
routine 17 notifying 10
NotificationBoard.NotifyLess keyX (value >= threshold): 10.18 >= 10
routine 17 notifying 9
NotificationBoard.NotifyLess keyX (value < threshold): 9.18 < 10 : found 10.18
--------------> notifying keyX < 10 (9.18)
routine 17 notifying 8
NotificationBoard.NotifyLess keyX (value < threshold): 8.18 < 10 : found 9.18
routine 17 notifying 7
NotificationBoard.NotifyLess keyX (value < threshold): 7.18 < 10 : found 8.18
routine 17 notifying 6
NotificationBoard.NotifyLess keyX (value < threshold): 6.18 < 10 : found 7.18
routine 15 waiting preparation...
routine 16 waiting preparation...
...... continues

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

...
NotificationBoard.NotifyLess keyX (value >= threshold): 10.22 >= 10
routine 21 notifying 9
NotificationBoard.NotifyLess keyX (value >= threshold): 10.07 >= 10
routine 6 notifying 9
NotificationBoard.NotifyLess keyX (value < threshold): 9.08 < 10 : found 10.07
--------------> notifying keyX < 10 (9.08)
routine 7 notifying 8
NotificationBoard.NotifyLess keyX (value < threshold): 9.17 < 10 : found 9.08
routine 16 notifying 8
NotificationBoard.NotifyLess keyX (value >= threshold): 10.11 >= 10
routine 10 notifying 9
NotificationBoard.NotifyLess keyX (value < threshold): 9.3 < 10 : found 10.11
--------------> notifying keyX < 10 (9.3)
routine 29 notifying 8
NotificationBoard.NotifyLess keyX (value < threshold): 9.19 < 10 : found 9.3
routine 18 notifying 8
...

что показывает, что они также находили предыдущие значения из других горутин.

Я почти уверен, что это основная проблема согласования, но я не мог ее обнаружить. :(

Я использую:

> go version
go version go1.10.2 windows/amd64

какие-нибудь идеи?

Есть много кода, который нужно прочитать и понять. Я предлагаю запустить приложение с детектором гонок и отчитаться. Будет полезно знать, работает ли код без гонок или есть гонка, где она расположена.

Cerise Limón 13.09.2018 19:29

Я запустил "go test -race" для этого пакета, и никакой гонки данных обнаружено не было. тесты только что «прошли»

weeanon 13.09.2018 19:38

Я не вижу ошибок в вашем выводе ... если только я не слепой. Опубликованный вами вывод выглядит как горутины, которые выполняются последовательно. Какой конкретный результат показывает сбой?

RayfenWindspear 13.09.2018 19:59
1
3
1 976
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Одним из способов упростить логику здесь может быть запуск одной горутины, которая изменяет карту. Затем он может прослушивать новые значения на канале (поскольку все должно быть в порядке, если значения обрабатываются последовательно). Вам нужно быть осторожным, чтобы знать, когда ваша горутина вернется, чтобы убедиться, что она не протекает. В общем, вы не должны обмениваться данными между горутинами, вы должны использовать каналы для общаться между горутинами. https://gobyexample.com/channels - хорошее вступление к каналам. https://blog.golang.org/share-memory-by-communicating - хорошее объяснение идиомы: «Не общайтесь, разделяя память; вместо этого делитесь памятью, общаясь».

Вот пример того, как можно реализовать этот тип приложения с использованием каналов, а не совместного использования памяти (версия для детской площадки).

package main

import (
    "fmt"
    "sync"
)

type value struct {
    key       string
    value     float64
    threshold float64
}

func main() {
    b := board{
        last: map[string]float64{},
    }
    c := b.start()
    wg := sync.WaitGroup{}
    for i := 0; i < 30; i++ {
        wg.Add(1)
        go func(i int) {
            for j := 15.0; j > 5; j-- {
                c <- value{"k1", j + float64(i+1)/100, 10}
            }
            wg.Done()
        }(i)
    }
    wg.Wait()
    close(c)
}

type board struct {
    last map[string]float64
}

func (b *board) start() chan<- value {
    c := make(chan value)
    go func() {
        for v := range c {
            b.notify(v)
        }
    }()
    return c
}

func (b *board) notify(v value) {
    if l, ok := b.last[v.key]; !ok || l >= v.threshold {
        if v.value < v.threshold {
            fmt.Printf("%s < %v (%v)\n", v.key, v.threshold, v.value)
        }
    }
    b.last[v.key] = v.value
}

фиксированный! Кроме того, я заметил, что мой первоначальный тест был несправедливым. Я начал много горутин без задержки, поэтому иногда некоторые процедуры сильно отставали от других и отправляли образцы выше порогового значения. Я имею в виду, что мои образцы не были постоянно монотонно уменьшающимися в моем тесте (вызывая зашумленные журналы). так или иначе, я изменил свою реализацию, чтобы использовать каналы. спасибо: D

weeanon 13.09.2018 21:08

Я думаю, вам нужно установить флаги, когда вы устанавливаете такие трекеры, один для случаев, когда значение увеличивается, а другой - для снижения. Я реализовал один

package main

import (
    "fmt"
    "sync"
)

const (
    threshold      int = 10
    upperThreshold int = 20
)

var mu sync.Mutex
var downwatch bool
var upwatch bool

func main() {
    var tracker int = 10
    var temp int = 1
    var sign int = 1
    for i := 1; i < 20; i++ {
        sign = sign * -1
        temp = temp + i
        go UpdateTracker(&tracker, temp*sign)
    }

    for {
    }
    return
}
func SetDownWatch() {
    downwatch = true
}
func SetUpWatch() {
    upwatch = true
}
func UnSetDownWatch() {
    downwatch = false
}
func UnSetUpWatch() {
    upwatch = false
}

func UpdateTracker(tracker *int, val int) {
    mu.Lock()
    defer mu.Unlock()
    if !(upwatch || downwatch) {
        if (*tracker)+val < threshold {
            NotifyOnDrop()
            SetDownWatch()
        }
        if (*tracker + val) > upperThreshold {
            NotifyOnRise()
            SetUpWatch()
        }

    }
    if (*tracker)+val < threshold && upwatch {
        NotifyOnDrop()
        SetDownWatch()
        UnSetUpWatch()
    }

    if (*tracker+val) > upperThreshold && downwatch {
        NotifyOnRise()
        SetUpWatch()
        UnSetDownWatch()
    }

    *tracker = (*tracker) + val
    fmt.Println((*tracker))
    return
}

func NotifyOnDrop() {
    fmt.Println("dropped")
    return
}

func NotifyOnRise() {
    fmt.Println("rose")
    return
}

updateTracker работает в обычном режиме и выводит на консоль печать, когда значение превышает установленный порог. Я думаю, что это именно та функциональность, которую вы искали, но здесь не хватает функции Last.Store, которая, как мне кажется, настраивается на ваш код. Я уверен, что есть другие способы справиться с этим. Этот мне показался достаточно простым.

Другие вопросы по теме