4

我有一个在 golang 中计算 SHA1 并打印以两个零开头的程序的程序。我想使用 goroutine 和通道。我的问题是,如果我不知道它会产生多少结果,我不知道如何优雅地退出 select 子句。

许多教程提前知道并在反击时退出。其他人建议使用 WaitGroups,但我不想这样做:我想在主线程中打印结果一出现在频道中。有人建议在 goroutines 完成时关闭通道,但我想在异步完成后关闭它,所以我不知道如何。

请帮助我实现我的要求:

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 %v\n ", hash)
            }
    }
    fmt.Printf("Count of %v sha1 took %v\n", 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}
    }
}
4

2 回答 2

3

首先:如果不知道你的计算什么时候结束,你怎么能建模呢?确保您确切知道程序终止的时间和情况。如果你完成了,你就会知道如何用代码编写它。

您基本上是在处理生产者-消费者问题。一个标准的案例。我会以这种方式建模(在游戏中):

制片人

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)
}

显然你是暴力破解哈希,所以当循环完成时就到达了终点。我们可以在此处关闭通道,并向其他 goroutine 发出信号,告知没有更多可听的内容。

消费者

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)。如果它关闭了,我们就完成了。否则打印接收到的哈希。

主要的

为了开始这一切,我们可以写:

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目的是等到生成的 goroutine 完成,由 goroutine 中的调用发出信号wg.Done

边注

另请注意,Rand您正在使用的并发访问不安全。使用全局初始化的math/rand. 例子:

rand.Seed(time.Now().UnixNano())
rand.Int()
于 2014-02-14T16:11:14.433 回答
2

您的程序的结构可能应该重新检查。这是我认为您正在寻找的工作示例。它可以在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
}

编辑:这不会为每个哈希计算启动一个单独的例程,但老实说,我看不到这样做的价值。所有这些例程的调度可能会比在单个例程中运行代码花费更多。如果需要,您可以将其拆分为 N 个例程的块,但 1:1 映射不是解决此问题的方法。

于 2014-02-14T16:07:36.853 回答