如何在Golang中优雅地跳出select语句

huangapple go评论95阅读模式
英文:

How to break out of select gracefuly in golang

问题

我有一个使用Go语言编写的程序,它计算SHA1并打印以两个零开头的结果。我想使用goroutines和channels。我的问题是,如果我不知道它会产生多少结果,我不知道如何优雅地退出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)
    }
    // 在这里关闭通道?我不能这样做,因为异步生产者现在正在工作

    for {
        select {
            // 如果没有生产者了,如何停止接收?
            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}
    }
}
英文:

I have a program in golang that counts SHA1s and prints ones that start with two zeros. I want to use goroutines and channels. My problem is that I don't know how to gracefully exit select clause if I don't know how many results it will produce.

Many tutorials know that in advance and exit when counter hits. Other suggest using WaitGroups, but I don't want to do that: I want to print results in main thread as soon it appears in channel. Some suggest to close a channel when goroutines are finished, but I want to close it after asynchronous for finishes, so I don't know how.

Please help me to achieve my requirements:

package main

import (
    &quot;crypto/sha1&quot;
    &quot;fmt&quot;
    &quot;time&quot;
    &quot;runtime&quot;
    &quot;math/rand&quot;
)

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(&quot;Number of CPUs: &quot;, nCPU)
    start := time.Now()

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

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

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

    if (hash[0] == 0 &amp;&amp; hash[1] == 0) {
        channel &lt;- Hash{random, hash}
    }
}

答案1

得分: 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()
英文:

Firstly: if you don't know when your computation ends, how could you even model it? Make sure you know exactly when and under what circumstances your program terminates. If you're done you know how to write it in code.

You're basically dealing with a producer-consumer problem. A standard case. I would model
that this way (on play):

Producer

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

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

		if hash[0] == 0 &amp;&amp; hash[1] == 0 {
			out &lt;- Hash{random, hash}
		}
	}

	close(out)
}

Obviously you're brute-forcing hashes, so the end is reached when the loop is finished.
We can close the channel here and signal the other goroutines that there is nothing more to listen for.

Consumer

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

	for {
		hash, ok := &lt;-in

		if !ok {
			break
		}

		fmt.Printf(&quot;Hash is %v\n &quot;, hash)
	}
}

The consumer takes all the incoming messages from the in channel and checks if it was closed (ok).
If it is closed, we're done. Otherwise print the received hashes.

Main

To start this all up we can write:

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

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

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

wg.Wait()

The WaitGroup's purpose is to wait until the spawned goroutines finished, signalled by
the call of wg.Done in the goroutines.

Sidenote

Also note that the Rand you're using is not safe for concurrent access. Use the one initialized
globally in math/rand. Example:

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

答案2

得分: 2

你的程序结构可能需要重新审查一下。这是一个我认为符合你要求的工作示例。可以在Go playground上运行。

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的映射方式。

英文:

The structure of your program should probably be re-examined.
Here is a working example of what I presume you are looking for.
It can be run on the Go playground

package main

import (
	&quot;crypto/sha1&quot;
	&quot;fmt&quot;
	&quot;math/rand&quot;
	&quot;runtime&quot;
	&quot;time&quot;
)

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

const Max int = 100000

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

	fmt.Println(&quot;Number of CPUs: &quot;, nCPU)

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

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

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

func Generate() &lt;-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 &lt; Max; i++ {
			random := fmt.Sprintf(&quot;This is a test %v&quot;, generator.Int())
			hash := sha1.Sum([]byte(random))

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

	return c
}

Edit: This does not fire up a separate routine for each Hash computation,
but to be honest, I fail to see the value on doing so. The scheduling of all those routines will likely cost you far more than running the code in a single routine.
If need be, you can split it up into chunks of N routines, but a 1:1 mapping is not the way to go with this.

huangapple
  • 本文由 发表于 2014年2月14日 23:32:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/21783333.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定