Как вырваться из выберите 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 ответа:
Во-первых: если Вы не знаете, когда заканчивается ваше вычисление, как вы можете его даже смоделировать? Убедитесь, что вы точно знаете, когда и при каких обстоятельствах ваша программа завершается. Если вы закончили, вы знаете, как написать его в коде.
Вы в основном имеете дело с проблемой производителя-потребителя. Стандартный случай. Я бы моделировал вот так (по игре):Производитель
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-это не тот путь, по которому нужно идти.