Как вырваться из выберите gracefuly в golang


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

Многие учебники знают это заранее и выходят, когда счетчик бьет. Другие предлагают использовать WaitGroups, но я не хочу этого делать: я хочу печатать результаты в основном потоке, как только он появится в канале. Некоторые предлагают закрыть канал, когда горотины закончены, но я хочу закрыть его после асинхронного завершения, поэтому я не знаю, как это сделать.

Пожалуйста, помогите мне выполнить мои требования:

package main

import (
    "crypto/sha1"
    "fmt"
    "time"
    "runtime"
    "math/rand"
)

type Hash struct {
    message string
    hash [sha1.Size]byte

}

var counter int = 0
var max int = 100000
var channel = make(chan Hash)
var source = rand.NewSource(time.Now().UnixNano())
var generator = rand.New(source)

func main() {
    nCPU := runtime.NumCPU()
    runtime.GOMAXPROCS(nCPU)
    fmt.Println("Number of CPUs: ", nCPU)
    start := time.Now()

    for i := 0 ; i < max ; i++ {
        go func(j int) {
            count(j)
        }(i)
    }
    // close channel here? I can't because asynchronous producers work now

    for {
        select {
                    // how to stop receiving if there are no producers left?
            case hash := <- channel:
                fmt.Printf("Hash is %vn ", hash)
            }
    }
    fmt.Printf("Count of %v sha1 took %vn", max, time.Since(start))
}

func count(i int) {
    random := fmt.Sprintf("This is a test %v", generator.Int())
    hash := sha1.Sum([]byte(random))

    if (hash[0] == 0 && hash[1] == 0) {
        channel <- Hash{random, hash}
    }
}
2 4

2 ответа:

Во-первых: если Вы не знаете, когда заканчивается ваше вычисление, как вы можете его даже смоделировать? Убедитесь, что вы точно знаете, когда и при каких обстоятельствах ваша программа завершается. Если вы закончили, вы знаете, как написать его в коде.

Вы в основном имеете дело с проблемой производителя-потребителя. Стандартный случай. Я бы моделировал вот так (по игре):

Производитель

func producer(max int, out chan<- Hash, wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; i < max; i++ {
        random := fmt.Sprintf("This is a test %v", rand.Int())
        hash := sha1.Sum([]byte(random))

        if hash[0] == 0 && hash[1] == 0 {
            out <- Hash{random, hash}
        }
    }

    close(out)
}

Очевидно, что вы грубо форсируете хэши, поэтому конец достигается, когда цикл законченный. Мы можем закрыть канал здесь и дать сигнал другим горотинам, что слушать больше нечего.

Потребитель

func consumer(max int, in <-chan Hash, wg *sync.WaitGroup) {
    defer wg.Done()

    for {
        hash, ok := <-in

        if !ok {
            break
        }

        fmt.Printf("Hash is %v\n ", hash)
    }
}
Потребитель принимает все входящие сообщения из канала in и проверяет, был ли он закрыт (ok). Если она закрыта, нам конец. В противном случае распечатайте полученные хэши.

Main

Чтобы начать все это, мы можем написать:

wg := &sync.WaitGroup{}
c := make(chan Hash)

wg.Add(1)
go producer(max, c, wg)

wg.Add(1)
go consumer(max, c, wg)

wg.Wait()

Цель WaitGroup состоит в том, чтобы дождаться, пока порожденные горотины закончат, сигнализируя зов wg.Done в горутинах.

Sidenote

Также обратите внимание, что Rand, который вы используете, не безопасен для параллельного доступа. Используйте тот, который инициализирован глобально в math/rand. Пример:

rand.Seed(time.Now().UnixNano())
rand.Int()

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

package main

import (
    "crypto/sha1"
    "fmt"
    "math/rand"
    "runtime"
    "time"
)

type Hash struct {
    message string
    hash    [sha1.Size]byte
}

const Max int = 100000

func main() {
    nCPU := runtime.NumCPU()
    runtime.GOMAXPROCS(nCPU)

    fmt.Println("Number of CPUs: ", nCPU)

    hashes := Generate()
    start := time.Now()

    for hash := range hashes {
        fmt.Printf("Hash is %v\n ", hash)
    }

    fmt.Printf("Count of %v sha1 took %v\n", Max, time.Since(start))
}

func Generate() <-chan Hash {
    c := make(chan Hash, 1)

    go func() {
        defer close(c)

        source := rand.NewSource(time.Now().UnixNano())
        generator := rand.New(source)

        for i := 0; i < Max; i++ {
            random := fmt.Sprintf("This is a test %v", generator.Int())
            hash := sha1.Sum([]byte(random))

            if hash[0] == 0 && hash[1] == 0 {
                c <- Hash{random, hash}
            }
        }
    }()

    return c
}

Edit : это не запускает отдельную процедуру для каждого вычисления хэша, но, честно говоря, я не вижу смысла в этом. Планирование всех этих процедур, вероятно, будет стоить вам гораздо больше, чем выполнение кода в одной процедуре. Если нужно, вы можете разделить его на куски N рутины, но сопоставление 1:1-это не тот путь, по которому нужно идти.