Можно ли оставлять буферизованный канал с открытыми данными, когда нет приемника? - PullRequest
0 голосов
/ 31 мая 2018

Предположим, есть 10 отправителей и один получатель для канала.Функция на стороне отправителя требует времени для возврата значения.Приемник хочет получить только одно значение (первое полученное значение) от канала, а остальные 9 значений не используются.Приемнику не нужно ждать остальных 9 значений.Вот почему я не использовал sync.WaitGroup.

Я использовал буферизованный канал, поэтому в буферизованном канале будет 9 данных, когда приемник выберет только первый.Мои вопросы:

  1. Можно ли оставлять буферизованный канал с открытыми данными, когда нет приемника?Следующий пример кода является упрощенным, но если программа является демоном, будет ли она в конечном итоге собрана сборщиком мусора?

  2. Есть ли лучшие способы справиться с такой ситуацией?Я пытался использовать канал отмены, но не смог.И я не уверен, что context подходит для этой ситуации.

Пример кода:

package main

import (
    "errors"
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())

    i, err := getRandomInt()
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(i)
    }

    fmt.Println("Waiting goroutines to be finished...")
    time.Sleep(2 * time.Second)
}

func getRandomInt() (int, error) {
    ch := make(chan int, 10)

    // 10 senders
    for i := 0; i < 10; i++ {
        go func(i int) {
            defer fmt.Printf("Goroutine #%d finished\n", i)
            fmt.Printf("Goroutine #%d started\n", i)

            data := heavyJob()
            ch <- data
            fmt.Printf("Goroutine #%d sent data %d to ch\n", i, data)
            return
        }(i)
    }

    // 1 receiver
    timeout := time.After(2000 * time.Millisecond)
    for {
        select {
        case value := <-ch:
            // uses only the value received first, the rest are discarded
            return value, nil
        case <-timeout:
            return -1, errors.New("Timeout")
        }
    }
}

// takes 1900~2900ms to finish
func heavyJob() int {
    r := rand.Intn(1000)
    time.Sleep(time.Duration(r+1900) * time.Millisecond)
    return r
}

Бег на детской площадке

1 Ответ

0 голосов
/ 31 мая 2018

Чтобы ответить на основные вопросы:

  1. Это нормально, чтобы покинуть канал, это будет мусор.
  2. Это кажется основанным на мнении, но для меня, если толькопричина, по которой вы создаете буферизованный канал с 10 пробелами, заключается в том, что программы-отправители могут выйти;Такое ощущение, что это выиграет от редизайна.Существуют и другие (возможно, более эффективные) способы обеспечения того, чтобы программы-отправители могли закрываться.

По сути, вы создаете неявную связь между количеством рабочих и размером буферизованного канала.Измените одно из этих двух чисел, и что-то зафиксируется / сломается!(В качестве примечания, буферизованные каналы обычно используются в тех случаях, когда потребитель и производители работают с одинаковой скоростью, но у одного нет стабильного выхода. Он колючий, буфер сглаживает пики и впадины.)

Имея это в виду, я рекомендую, чтобы было явным по поводу управления тем фактом, что вам не нужны все значения.

Вот обновленная версия getRandomInt () функция.Обратите внимание на настройку отмены контекста с помощью defer вверху и использование оператора select при отправке.

func getRandomInt() (int, error) {
    ctx := context.Background() // creates a fresh, empty context
    ctx, cancel := context.WithCancel(ctx)
    defer cancel() // cancels the context when getRandomInt() returns

    ch := make(chan int)

    // 10 senders
    for i := 0; i < 10; i++ {
        go func(i int) {
            defer fmt.Printf("Goroutine #%d finished\n", i)
            fmt.Printf("Goroutine #%d started\n", i)

            data := heavyJob()

            // this select statement wil block until either this goroutine 
            // is the first to send, or the context is cancelled. In which case
            // another routine has already sent and it can discard it's values.
            select { 
            case ch <- data:
                fmt.Printf("Goroutine #%d sent data %d to ch\n", i, data)
            case <-ctx.Done():
                fmt.Printf("Goroutine #%d did not send, context is cancelled, would have sent data %d to ch\n", i, data)
            }
        }(i)
    }

    // 1 receiver
    timeout := time.After(2000 * time.Millisecond)
    select {
    case value := <-ch:
        // uses only the value received first, the rest are discarded
        return value, nil
    case <-timeout:
        return -1, errors.New("Timeout")
    }
}

Настройка контекста с отменой означает, что контекст становится «Готово» после cancel() функция вызывается.Это способ сказать всем подпрограммам отправителя не беспокоиться об ожидании отправки.

При отправке оператор выбора блокируется до тех пор, пока какой-либо контекст не будет отменен функцией cancel();или метод получателя читает первое значение.

Я также удалил буферизацию из канала, так как больше в этом нет необходимости.

...