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

Я пытаюсь закодировать структуру уведомлений в 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 ….» происходит дважды, только в моменты, когда выборка опускается ниже порогового значения.

См. также:  Не удалось создать правильную подпись для ответа SAML

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

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
...

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

См. также:  получить значение от контроллера для формирования Symfony 3

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

Я использую:

> go version
go version go1.10.2 windows/amd64

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

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

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

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

Понравилась статья? Поделиться с друзьями:
IT Шеф
Комментарии: 2
  1. weeanon

    Одним из способов упростить логику здесь может быть запуск одной горутины, которая изменяет карту. Затем он может прослушивать новые значения на канале (поскольку все должно быть в порядке, если значения обрабатываются последовательно). Вам нужно быть осторожным, чтобы знать, когда ваша горутина вернется, чтобы убедиться, что она не протекает. В общем, вы не должны обмениваться данными между горутинами, вы должны использовать каналы для связи между горутинами. 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 person weeanon; 13.09.2018

  2. weeanon

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

    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, которая, как мне кажется, настраивается на ваш код. Я уверен, что есть другие способы справиться с этим. Этот мне показался достаточно простым.

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: