Go разветвление, многократные трансляции, неизвестное количество получателей - PullRequest
1 голос
/ 20 февраля 2020

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

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

type signal struct {
    data     []int
    channels []chan struct{}
}

func newSignal() *signal {
    s := &signal{
        data:     make([]int, 0),
        channels: make([]chan struct{}, 1),
    }
    s.channels[0] = make(chan struct{})
    return s
}

func (s *signal) Broadcast(d int) {
    s.data = append(s.data, d)
    s.channels = append(s.channels, make(chan struct{}))
    close(s.channels[len(s.data)-1])
}

func test(s *signal, wg *sync.WaitGroup, id int, ctx context.Context) {
    for i := 0; ; i += 1 {
        select {
        case <-s.channels[i]:
            if id >= s.data[i] {
                fmt.Println("Goroutine completed:", id)
                wg.Done()
                return
            }
        case <-ctx.Done():
            fmt.Println("Goroutine completed:", id)
            wg.Done()
            return
        }
    }
}

func main() {
    s := newSignal()

    ctx, cancel := context.WithCancel(context.Background())
    wg := sync.WaitGroup{}
    wg.Add(3)
    go test(s, &wg, 3, ctx)
    go test(s, &wg, 2, ctx)
    go test(s, &wg, 1, ctx)

    s.Broadcast(3)
    time.Sleep(1 * time.Second)

    // multiple broadcasts is mandatory
    s.Broadcast(2)
    time.Sleep(1 * time.Second)

    // last goroutine
    cancel()

    wg.Wait()
}

Детская площадка: https://play.golang.org/p/dGmlkTuj7Ty

Есть ли более элегантный способ сделать это? Тот, который использует только встроенные библиотеки. Если нет, то безопасное ли это решение? По крайней мере, я считаю, что это безопасно, так как оно работает для большого количества программ (я провёл с ним некоторое тестирование).

Чтобы быть кратким, вот что я хочу:

  • Основная программа (назовите ее M) должна иметь возможность сигнализировать с некоторыми данными (назовите ее d) о некотором неизвестном количестве других программ (назовите их n для 0...n), несколько раз, с каждая процедура, выполняющая действие на основе d каждый раз
  • M, должна иметь возможность сигнализировать все о других n программах с определенными (числовыми) данными, несколько раз
  • Каждая процедура в n будет либо завершаться самостоятельно (в зависимости от контекста), либо после выполнения некоторой операции с d и определения ее судьбы. Он будет выполнять эту проверку столько раз, сколько ему было сообщено, пока он не умрет.
  • Мне не разрешено каким-либо образом отслеживать n программы (например, имея карту каналы для перебора и итерации)

В моем решении срезы каналов не представляют подпрограммы: они фактически представляют сигналы, которые транслируются. Это означает, что если я транслирую два раза, а затем раскручивается программа, он проверит оба сигнала перед сном в блоке select.

1 Ответ

1 голос
/ 20 февраля 2020

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

По сути, вам нужно что-то, скажем Broadcaster, которое хранит список каналов. Когда вы звоните Broadcaster.send(data), он проходит по списку каналов, отправляющих data по каждому каналу. Broadcaster также должен иметь путь для goroutines до subscribe до Broadcaster. У перехватчиков должен быть способ принять канал от Broadcaster или назначить канал для Broadcaster. Этот канал является связующим звеном.

Если работа, выполняемая в процедурах «наблюдатель», займет много времени, рассмотрите возможность использования буферизованных каналов, чтобы Broadcaster не блокировался в течение send и ожидание выполнения процедур. Если вам не важно, пропустил ли в программе data, вы можете использовать неблокирующую отправку (см. Ниже).

Когда «умирает» подпрограмма, она может unsubscribe с Broadcaster который удалит соответствующий канал из своего списка. Или канал может просто оставаться заполненным, и Broadcaster придется использовать неблокирующую передачу , чтобы пропустить полные каналы до мертвых программ.

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

...