Я пытаюсь закодировать структуру уведомлений в 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
какие-нибудь идеи?
Есть много кода, который нужно прочитать и понять. Я предлагаю запустить приложение с детектором гонок и отчитаться. Будет полезно знать, работает ли код без гонок или есть гонка, где он расположен. — person weeanon schedule 13.09.2018
Я запустил go test -race для этого пакета, и никакой гонки данных обнаружено не было. тесты только что прошли — person weeanon schedule 13.09.2018
Я не вижу ошибок в вашем выводе … если только я не слепой. Опубликованный вами вывод выглядит как горутины, которые выполняются последовательно. Какой конкретный результат показывает сбой? — person weeanon schedule 13.09.2018
Одним из способов упростить логику здесь может быть запуск одной горутины, которая изменяет карту. Затем он может прослушивать новые значения на канале (поскольку все должно быть в порядке, если значения обрабатываются последовательно). Вам нужно быть осторожным, чтобы знать, когда ваша горутина вернется, чтобы убедиться, что она не протекает. В общем, вы не должны обмениваться данными между горутинами, вы должны использовать каналы для связи между горутинами. https://gobyexample.com/channels — хорошее введение в каналы. https://blog.golang.org/share-memory-by-communicating — хорошее объяснение идиомы: «Не общайтесь, разделяя память; вместо этого делитесь памятью, общаясь».
Вот пример того, как можно реализовать этот тип приложения с использованием каналов, а не совместного использования памяти (версия для игровой площадки < / а>).
фиксированный! Кроме того, я заметил, что мой первоначальный тест был несправедливым. Я начал много горутин без задержки, поэтому иногда некоторые процедуры сильно отставали от других и отправляли образцы выше порогового значения. Я имею в виду, что мои образцы не были постоянно монотонно уменьшающимися в моем тесте (вызывая зашумленные журналы). так или иначе, я изменил свою реализацию, чтобы использовать каналы. спасибо: D — person weeanon; 13.09.2018
Я думаю, вам нужно установить флаги, когда вы устанавливаете такие трекеры: один для случаев, когда значение увеличивается, а другой — для снижения. Я реализовал один
updateTracker
работает как обычная процедура и выводит на консоль, когда значение превышает установленный порог. Я думаю, что это именно та функциональность, которую вы искали, но здесь не хватает функцииLast.Store
, которая, как мне кажется, настраивается на ваш код. Я уверен, что есть другие способы справиться с этим. Этот мне показался достаточно простым.