<p>tl;dwtr (too long; don't want to read): Last paragraph has my question.</p>
<p>I am currently trying to get better acquainted with Go by writing what amounts to a benchmarking suite. However, I am not running a single-core computer, and I am trying to find a way to keep my processor pegged at 100 (or as close to it as possible) without it dipping down randomly, and I want to do it on as many threads as possible.</p>
<p>So, what have I done so far? Well, here's some pseudo code to give you an idea:</p>
<pre><code>func main(){
for i < maxNumber{
runBenchmark(i)
}
}
func runBenchmark(integer){
//do a lot of something for a long time.
}
</code></pre>
<p>Obviously I have other things in there, like tracking execution time and stuff like that. But this is the core of what I'm trying to distribute. Now, that <code>maxNumber</code> is a pretty big number. It's actually a 64-bit unsigned integer, so, yeah, I'm running a <em>lot</em> of numbers. The problem I'm running in to (which actually isn't a problem, but a feature of Go, as far as I understand) is that I'm not actually maxing out my CPU at all. It gets close, and often does use a full CPU while in a virtual machine, but in the end, I can't get it to put load on all of the cores on my processor at once. </p>
<p>My obvious answer to this is "Take the max number, divide it up into even chunks, and run those chunks as goroutines in parallel." But when I tried that, cores would end up finishing before others, because the nature of the benchmark that I'm running is that each computation takes wildly different amounts of time (but does so deterministically).</p>
<p>Now we get to where I cant seem to find a good answer. I want to figure out a way to dynamically spin up a new call of <code>runBenchmark()</code> whenever a core/thread finishes working on its last task. But everything I find out there either says "Don't do it" or is vague and/or over my head. I haven't been able to get any of the examples working correctly. </p>
<p>And so, my question: What, if any, is the accepted way of dynamically creating goroutines whenever a thread becomes available? Or, maybe I'm asking the wrong question. Maybe I'm not understanding something about goroutines. I know they're not quite like threads in other languages, but as far as I can tell, they work roughly the same way at a basic level (after all, I have no blocking IO or anything like that).</p>
<p>EDIT: Fixed my question. Whoops. it was "What, if any, is the accepted way of having goroutines use as many threads as possible?" Made it more clear.</p>
<hr/>**评论:**<br/><br/>sthussey: <pre><p>I would make runBenchmark load its integer input off a channel. Then run one goroutine that loads your values of i into the channel and #_of_CPU goroutines of runBenchmark will continue to load new inputs from the channel as long as it is open. That should keep the goroutines running as long as you have input for your benchmark.</p></pre>b4ux1t3: <pre><p>Thanks a lot! You and <a href="/u/Crast" rel="nofollow">/u/Crast</a> have helped me make a big connection in how concurrency works. I thought channels and goroutines were separate things, so I was focusing on goroutines first and then I was going to work on channels. You guys have helped a lot! </p></pre>Crast: <pre><p>To be fair, they are separate things, you can use one without the other, but the important thing to recognize is that communication is the key part here. </p>
<p>When you start a goroutine, you can initialize it passing parameters using the syntax <code>go somefunc(param1, param2)</code> that is a form of communication. But after you've done that, you've got no way to kill that goroutine, or get a handle on that goroutine, and that communication only happened once.</p>
<p>Channels facilitate by allowing you to create a (optionally buffered) pipe that can be both written to and read from multiple goroutines, and sending goroutines theoretically don't have to know who's listening on the other end of the channel, giving you a clean way to communicate a continuous stream of values.</p></pre>Crast: <pre><blockquote>
<p>Now we get to where I cant seem to find a good answer. I want to figure out a way to dynamically spin up a new call of runBenchmark() whenever a core/thread finishes working on its last task.</p>
</blockquote>
<p>This is a pretty good use case for channels. Feed your jobs into a channel, have some amount of goroutines reading off the channel and performing the work. Each goroutine could simply loop reading the channel, this way when it's done with a job, it picks up the next.</p>
<p>You can then experiment with the ideal value by re-running with different numbers of goroutines as until you find your number for ideal throughput.</p></pre>b4ux1t3: <pre><p>. . .Oh. <em>That's</em> what a channel is. That makes so much sense. Okay. I just made a huge leap in understanding. Thank you so much. </p></pre>s-expression: <pre><p>Here's a small example that feeds numbers to a pool of workers, and waits until all the work has completed:</p>
<pre><code>numbers := make(chan int, 16)
var wg sync.WaitGroup
// Spawn 6 worker goroutines
for i := 0; i < 6; i++ {
wg.Add(1)
go func() {
for num := range numbers {
runBenchmark(num) // worker goroutines will stay in this loop until there is no more work available on the channel
}
wg.Done()
}()
}
// Feed numbers to worker pool
for i := 0; i < maxNumber; i++ {
numbers <- i
}
close(numbers)
// wait for completion
wg.Wait()
</code></pre>
<p>This is a simple way to throttle parallelism.</p></pre>b4ux1t3: <pre><p>Cool! Quick question: Do you know of a way to check the maximum number of possible <del>goroutines</del> threads on any given system?</p>
<p>For instance, if I know that I won't have any blocking IO, and the system has n <del>cores</del> threads, is there a way to tell Go that I want it to always have n-1 threads active? Like,</p>
<pre><code>numRoutines = sys.getNumThreads() - 1
// Spin up that number of goroutines
</code></pre>
<p>I know that's a long shot, since Go is basically designed to keep you from shooting yourself in the foot when itcomes to parallelization. </p></pre>s-expression: <pre><p>To be honest I'm not sure. What I do know is that the go runtime is much better than the programmer at figuring out the optimal mapping between goroutines and system threads.</p>
<p>For any given task, goroutines may be <em>much</em> faster than spinning up a kernel thread for each worker. Especially if the work is short lived.</p></pre>b4ux1t3: <pre><p>Fair enough. Obviously my use-case is fairly rare. And, in the end, Go is better off <em>fighting</em> what I'm tying to do in the big picture, at least as it relates to general software development. Like you said, Go is likely very good at spreading goroutines, since that's literally what it's built to do.</p></pre>PsyWolf: <pre><p>What you're looking for is <a href="https://golang.org/pkg/runtime/#NumCPU" rel="nofollow">https://golang.org/pkg/runtime/#NumCPU</a></p>
<p>That said, it's generally safe to spawn way more goroutines than logical CPUs and let the runtime worry about how to schedule them. </p>
<p>As long as you don't ever have LESS goroutines doing work than you have logical cores, the runtime should keep your CPU relatively hot.</p></pre>PsyWolf: <pre><p>Also, as of go 1.5 GOMAXPROCS is set to the value of runtime.NumCPU() by default, which is what you want here. But it was defaulted to 1 in earlier versions, so make sure you're running the latest version of go!</p>
<p>See <a href="https://docs.google.com/document/d/1At2Ls5_fhJQ59kDK2DFVhFu3g5mATSXqqV5QrxinasI/edit?usp=docslist_api" rel="nofollow">https://docs.google.com/document/d/1At2Ls5_fhJQ59kDK2DFVhFu3g5mATSXqqV5QrxinasI/edit?usp=docslist_api</a> for more info</p></pre>b4ux1t3: <pre><p>Thanks a lot! Sat down just now to look into it myself, and boom, there was your reply. Today is my lucky day, it seems.</p></pre>PsyWolf: <pre><p>We try to be a helpful community.
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传