Help understanding this prime sieve (concurrent)

agolangf · · 579 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Link: <a href="http://play.golang.org/p/9U22NfrXeq" rel="nofollow">http://play.golang.org/p/9U22NfrXeq</a></p> <p>I&#39;m very new to Go, and to concurrency in general. Can you help teach me how to reason about the above code? I&#39;ve watched Rob Pike&#39;s video about concurrency patterns and feel like I understand almost all of it, but here are my specific questions about the prime sieve above:</p> <p>1) What is line 32 doing (ch = ch1)? What is the effect of reassigning the ch channel like this? Is the Generate goroutine now referencing ch1?</p> <p>2) Why define the Filter function with read-only and write-only channels for parameters, rather than just channels? Does it change the code&#39;s behavior, or is it only for best practice?</p> <p>3) If line 17 returns false (i.e. when i%prime == 0) and line 18 never gets run, why doesn&#39;t that block the whole function? I have a sense that it is the swap on line 32 that gets around this, but I&#39;m not quite sure I really understand it.</p> <p>If anyone has time to help me out, I would really appreciate it. Thanks!</p> <hr/>**评论:**<br/><br/>Fwippy: <pre><p>1) <code>ch = ch1</code> - this is just updating the <code>ch</code> variable so that the next time through the loop, the new goroutine gets that channel to read from. If the channels were named a, b, c... we spin up a Generator that writes to channel A. The first time through the loop, we make a channel B, and set a goroutine to read from A and write to B. We store &#34;B&#34; in <code>ch</code>, and next time we set a new goroutine to read from B and write to C, and so on and so forth.</p> <p>2) Clarity &amp; protection against programmer error. Code works the same if you don&#39;t specify them as read-only or write-only.</p> <p>3) We&#39;ve already read from the input channel (line 16). If it&#39;s divisible by <code>prime</code>, we simply don&#39;t send <code>i</code> to the output channel and discard its value (because we know it&#39;s not prime).</p></pre>HappyHappySisyphus: <pre><p>Thanks for the response. What&#39;s throwing me, though, is when we reassign ch=ch1, why does that not lose reference to the channel being updated in Generate? i.e. Generate is updating some channel (&#34;ch&#34;), but when we reassign what &#34;ch&#34; points to, how does Generate know to follow along? Why doesn&#39;t it continue to update that original channel that is no longer referenced by &#34;ch&#34;?</p></pre>Fwippy: <pre><p>I think you&#39;re overthinking it a little bit, or perhaps applying lessons from other languages.</p> <p>When you pass something to a function, the receiving function gets a copy of whatever you&#39;re sending it. If you send an <code>int</code> it gets an <code>int</code>, if you send a pointer to an int, you get a copy of a pointer to that int.</p> <p>Even though channels internally are implemented as a reference to the important information, when you do <code>ch=ch1</code>, you&#39;re not updating what <code>ch</code> points to - you&#39;re updating what <code>ch</code> is. So if you have <code>ch = { realChannel &amp;memLocationA }</code>, <code>ch1 = { realChannel &amp;memLocationB }</code>, after assignment you have <code>ch = { realChannel &amp;memLocationB}</code>. So the realChannel at locationA has no idea and doesn&#39;t care that you&#39;ve changed <code>ch</code>, and the function&#39;s <code>in</code> chan still holds <code>{realChannel &amp;memLocationA}</code>.</p> <p>Think of it the same as:</p> <pre><code>count := 0 for { go fmt.Println(count) count ++ } </code></pre> <p>Even though <code>count</code> is getting updated each loop invocation, the receiving function doesn&#39;t know and doesn&#39;t care.</p></pre>HappyHappySisyphus: <pre><p>OK that makes perfect sense. Thank you!</p></pre>lapingvino: <pre><p>because the channel is a reference type, when you copy it you still reference the same channel. reassigning it with a newly created channel doesn&#39;t change the channel, it changes the variable assignment at that point.</p></pre>HappyHappySisyphus: <pre><blockquote> <p>when you copy it</p> </blockquote> <p>When does the code copy a channel? I see the reassignment on line 32, so even though Generate continues to update the original channel that &#34;ch&#34; used to point to, didn&#39;t we just reassign the only variable that pointed to that channel? How is that channel being referenced after we reassign &#34;ch&#34; to point at a new one? </p></pre>lapingvino: <pre><p>function invocation are pass by value, so when passing the channels to the function that gets started as a goroutine, you make a copy of the reference there to that channel. then by putting ch inside ch1 and getting a new value for ch you shift them one up for the next goroutine invocation.</p></pre>Ayiga: <pre><p>Perhaps I can help you understand this a bit more.</p> <p>What is happening in this example is essentially a large daisy-chain of filters.</p> <p>First, the Generate function simply passes a continually incrementing number into the channel it was passed. When go is called to spawn a goroutine, the variable at the state it was called is passed. This means that the line in the main function:</p> <pre><code>go Generate(ch) </code></pre> <p>will always reference the original channel created. This channel will keep getting and ever incrementing number of ints passed into it, from 2 until whenever the program stops.</p> <p>Next, the Filter function:</p> <pre><code>func Filter(in &lt;-chan int, out chan&lt;- int, prime int) { for { i := &lt;-in // Receive value from &#39;in&#39;. if i%prime != 0 { out &lt;- i // Send &#39;i&#39; to &#39;out&#39;. } } } </code></pre> <p>Will, until the program stops, read a number from the in channel, and if it is <strong>not</strong> a multiple of prime, it will send it through to the output.</p> <p>Now the key thing to remember here, is that the go routine spawns and will forever reference what was passed into it when it spawned. That means that the line</p> <pre><code>go Filter(ch, ch1, prime) </code></pre> <p>Within the main function, will always reference what channels ch, ch1, and prime were at the time of this goroutine spawn. </p> <p>To illustrate what&#39;s happening in the program, we just need to think about the order of what is happening. Now, it is important to know that channels that are created with no length specified will block on send and receive. This is the only point where a function&#39;s execution will pause, and wait for other things to begin. With that in mind, let&#39;s consider the execution of the program.</p> <ul> <li><em>main</em> - First, we have a channel, <strong>ch</strong>, that is created and passed into a newly spawned goroutine, <em>Generate</em>. This is the first goroutine spawned, so I will refer to it as <em>goRoutine1</em>.</li> <li><em>main</em> - At this point, the execution continues into the for loop, where we encounter the variable <strong>prime</strong>.</li> <li><em>main</em> - <strong>prime</strong> is created and is assigned to whatever <strong>ch</strong> has received.</li> <li><em>main</em> - At the moment, <strong>ch</strong> hasn&#39;t received anything.</li> <li><em>main</em> - So the <em>main</em> function pauses as the receive action on <strong>ch</strong> blocks, and now another goroutine is able to be run.</li> <li><em>goRoutine1</em>(<em>Generate</em>) - Since only the goroutine referencing <em>Generate</em> exists, the program switches to that.</li> <li><em>goRoutine1</em>(<em>Generate</em>) - starts it&#39;s execution, and iterates through the forloop. It sends the first value it encounters from <strong>i</strong>, in this case 2, through it&#39;s local channel <strong>ch</strong>, and continues.</li> <li><em>goRoutine1</em>(<em>Generate</em>) - tries to send another value <strong>i</strong>, in this case, 3, through the channel, but the channel only has the capacity for one number, so the send action blocks, and the goroutine is paused, allowing for something else to execute.</li> <li><em>main</em> - is now able to resume execution, and resolves the assignment to <strong>prime</strong> since it now has a value within the channel.</li> <li><em>main</em> - now prints out the <strong>prime</strong> which is 2, which is the first number we are given from <em>Generate</em>.</li> <li><em>main</em> - The program now creates a new channel <strong>ch1</strong>, which will act as the new channel we will receive future primes from.</li> <li><em>main</em> - The program spawns a new goroutine with the function <em>Filter</em>, passing it <strong>ch</strong>, <strong>ch1</strong>, and <strong>prime</strong>, as they currently exist. This is the second goroutine spawned, so I will refer to it as <em>goRoutine2</em>.</li> <li><em>main</em> - Continues, and assigns <strong>ch</strong> to <strong>ch1</strong>. The only thing this will affect is the next iteration of the forloop within <em>main</em>.</li> </ul> <p>All of this has been fairly straight forward at this point, and the execution after this is still straight forward, it&#39;s just a lot of context switching that occurs.</p> <ul> <li><em>main</em> - <strong>prime</strong> recieves from <strong>ch</strong>. <strong>ch</strong> in this case is the <strong>out</strong> channel from the <em>Filter</em> function of <em>goRoutine2</em>.</li> <li><em>main</em> - <strong>ch</strong> hasn&#39;t received anything so it blocks, and allows another goroutine to execute.</li> <li><em>goRoutine1</em>(<em>Generate</em>) - awakens, it&#39;s local <strong>ch</strong> is no longer blocking, and so it sends 3 to it.</li> <li><em>goRoutine1</em>(<em>Generate</em>) - tries to send 4 to <strong>ch</strong>, but <strong>ch</strong> is full and so it blocks, and allows another goroutine to execute.</li> <li><em>goRoutine2</em>(<em>Filter</em>) - begins it&#39;s execution.</li> <li><em>goRoutine2</em>(<em>Filter</em>) - assigns <strong>i</strong> from it&#39;s <strong>in</strong> channel argument. (This is the same channel that <em>goRoutine1</em>(<em>Generate</em>) is sending on.</li> <li><em>goRoutine2</em>(<em>Filter</em>) - <strong>i</strong> is assigned to 3, no blocking at this point, since 3 was already sent into the channel.</li> <li><em>goRoutine2</em>(<em>Filter</em>) - <strong>i</strong> is not divisible by <strong>prime</strong> (2)</li> <li><em>goRoutine2</em>(<em>Filter</em>) - <strong>i</strong> (3) is sent to <strong>out</strong></li> <li><em>goRoutine2</em>(<em>Filter</em>) - begins another iteration.</li> <li><em>goRoutine2</em>(<em>Filter</em>) - attempts to receive from <strong>in</strong> and store it in <strong>i</strong>.</li> <li><em>goRoutine2</em>(<em>Filter</em>) - <strong>in</strong> blocks, allowing another goroutine to execute.</li> <li><em>main</em> - <strong>prime</strong> receives from <strong>ch</strong>, in this case the value 3.</li> <li><em>main</em> - prints 3.</li> <li><em>main</em> - creates a new channel, <strong>ch1</strong> and spawns a new goroutine, <em>goRoutine3</em>. Passing the currently referenced variables, <strong>ch</strong>, <strong>ch1</strong>, and <strong>prime</strong>.</li> <li><em>main</em> - assigns <strong>ch</strong> to <strong>ch1</strong>, and loops.</li> <li><em>main</em> - receives into <strong>prime</strong> from <strong>ch</strong>.</li> <li><em>main</em> - <strong>ch</strong> hasn&#39;t received anything and it blocks, allowing other things to execute.</li> </ul> <p>I could keep going, but that will become very difficult to follow, if it hasn&#39;t already. The gist of what&#39;s happening is something like this: When we find a prime, we create a filter to help us consider only other things as primes. The filters are created whenever a new prime is found, and helps us to only consider numbers that pass the previous filters as potential primes. As each new filter is created, it receives number to test from each filter before it.</p> <p>That means that the filter for the prime 2 receives every number, and only returns every other one.</p> <pre><code>filter(in,out,2) // sends to out 3,5,7,9,11,13,... only odd numbers </code></pre> <p>Once a number gets through all currently existing filters, it is considered a new prime number, so we create a new filter with it, and hook it up to the previous filter. For a filter with the prime, 2, the next prime is 3. So the next prime we receive will have to get through both filters to be considered a prime.</p> <pre><code>filter(in,out,3) // receives from filter(in,out,2), sends to out 5,7,11,13,... only odd numbers not divisible by 3. </code></pre> <p>5 is the next prime, and the process repeats.</p> <p>filter2 -&gt; filter3 -&gt; filter5 -&gt;filter7 -&gt; filter11 -&gt; filter13 -&gt; filter17 -&gt; filter19 -&gt; filter23 -&gt; filter29, so on an so forth.</p> <p>The program only outputs the first 10 primes, and so execution stops here, but it doesn&#39;t need to. Essentially the main function is just reading <strong>prime</strong> from anything that has passed through all previous filters.</p> <p>As an aside, to try and be as clear as possible, when others are referring to pass by value they are referring to the function signatures not specifying a pointer value (denoted by *). Since these are not pointers, they are pass by value. This means that when the call enters the new function scope, they get a copy of the arguments. Even though the channels are copies of the original, they still refer to the same send and receive portions of the original channels created in the main function.</p> <p>I apologize for the length of the post, but I figured I&#39;d try to provide a couple of explanations to provide more detail in hopes that either, or both, help.</p></pre>HappyHappySisyphus: <pre><p>I am reading through this answer now, but I just wanted to tell you that I appreciate you taking the time to help me out with such a detailed post. I&#39;m reading through it now, thanks again for your help</p></pre>ChristophBerger: <pre><p>Visit <a href="http://divan.github.io/posts/go_concurrency_visualize/" rel="nofollow">http://divan.github.io/posts/go_concurrency_visualize/</a>, read about the basics of the visualizations demonstrated there, and then scroll down until you see the prime sieve example. You might find the 3d visualization of the goroutines and channel messages as useful as I did.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

579 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传