Why M:N scheduling instead of async...await?

blov · · 682 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Why did Go choose to implement M:N scheduling instead of co-routines like C#&#39;s <code>async..await</code>? What benefits does M:N scheduling offer that C#&#39;s <code>async...await</code> does not?</p> <p>I understand the implementation differences between them, but it looks like <code>async...await</code> model offers most of the benefits at a much lower overhead. For example, awaiting 1000 async operations doesn&#39;t require allocating 2MB of stack.</p> <hr/>**评论:**<br/><br/>TheMerovius: <pre><p>Disclaimer: I don&#39;t know C#. From what I&#39;ve seen by skimming <a href="https://msdn.microsoft.com/en-us/library/hh191443.aspx">these docs</a>, the async/await mechanism basically means &#34;promises&#34;?</p> <p>The advantage of go&#39;s concurrency model is not so much, that it makes it easier, to write concurrent code, but more that it makes it mostly unnecessary. You can write your APIs blocking and completely linear and let your caller use your blocking API in a non-blocking way. It basically gives you the power of having <em>all</em> functions being marked &#34;async&#34;, without having to write asynchronous code at all.</p> <p>In regards to the overhead, you should take into account not only the technical overhead, but also the cognitive. go basically trades off a small amount of memory and CPU time against a vast reduction in cognitive overhead. By not caring about being non-blocking, you free up the part of your brain that deals with &#34;how can I make this code non-blocking&#34;. You push dealing with concurrency up the stack, so to speak.</p> <p>Also note, that goroutines can be used for far more powerful stuff than sync/await. For example, you can spin off a long-running goroutine that periodically does work in the background. Or fire up an asynchronous computation whose actual results aren&#39;t interesting, only it&#39;s side-effects (so there basically is no need for an &#34;await&#34;).</p> <p>As an aside:</p> <blockquote> <p>For example, awaiting 1000 async operations doesn&#39;t require allocating 2MB of stack.</p> </blockquote> <p>That seems weird to me. How is async/await implemented if not as a less powerful abstraction over the M:N threading model? I am willing to take your word for it, but I am curious.</p></pre>TheMerovius: <pre><p>Okay, reading the documentation, I would still assume that they <em>do</em> need a separate stack for every async operation, but don&#39;t spawn a new thread (userspace or kernel). Because an async function can continue to run after the await, you need to preserve the stack frame and because the caller can call other functions before calling await itself, you need to do this on a separate stack. Or where am I wrong?</p></pre>gopherookie: <pre><p>It doesn&#39;t have to retain the entire stack. It only has to allocate the local variables used after <code>await</code> in the heap. This is similar to how the stack of outer goroutine need not be retained until the inner goroutine completes in the below Go code, <code>n</code> simply escapes to heap during compilation.</p> <pre><code>func main() { go func() { n := 10 go func() { time.Sleep(1 * time.Second) n = n + 1 fmt.Println(&#34;Incremented n in inner goroutine&#34;) }() fmt.Println(&#34;Exiting outer goroutine&#34;) }() time.Sleep(3 * time.Second) } </code></pre> <p>You can spin off a <code>Task</code> that runs every X amount of time without blocking a thread in C# too. This SO answer has an example <a href="http://stackoverflow.com/a/22453097" rel="nofollow">http://stackoverflow.com/a/22453097</a></p> <p>If you are not interested in results of an <code>async</code> method, you simply don&#39;t <code>await</code> on the returned task.</p> <p>Regarding cognitive overhead both lead to synchronous-looking code that does not block the OS thread for every concurrent IO. A lot of complexity in C# comes from the fact that it has both synchronous and asynchronous version of IO APIs, not from the <code>async...await</code> mechanism itself.</p></pre>TheMerovius: <pre><blockquote> <p>It only has to allocate the local variables used after await in the heap. </p> </blockquote> <p>What if you call a function after await? What if that function is recursive or dynamically dispatched? That sounds like a possible compiler optimization, but I would be (mildly) surprised if it&#39;s implemented well enough to matter. In particular, it definitely doesn&#39;t depend on the threading model, this optimization is equally possible with goroutines.</p> <blockquote> <p>Regarding cognitive overhead both lead to synchronous-looking code</p> </blockquote> <p>I strongly disagree. The example code I linked to contained several concurrency related keywords and concepts. And unless you litter async&#39;s <em>everywhere</em> you are dependent on the implementer of a function to give you an async variety (I was even surprised, that async is not simply the default). It is decidedly not the same as making synchronous code the default and easily composed in concurrent ways.</p> <blockquote> <p>A lot of complexity in C# comes from the fact that it has both synchronous and asynchronous version of IO APIs, not from the async...await mechanism itself.</p> </blockquote> <p>Without the async/await mechanism you don&#39;t <em>need</em> two varieties.</p> <p>Anyway, you asked about the benefits, I gave the best answer I had and I gain nothing from convincing you of my answers :)</p></pre>gopherookie: <pre><blockquote> <p>What if you call a function after await?</p> </blockquote> <p>It runs on an OS thread&#39;s stack like any other function call. The compiler arranges everything after <code>await</code> as a continuation. Think of it as promises/callbacks except that that compiler rearranges your code for that.</p> <blockquote> <p>Anyway, you asked about the benefits, I gave the best answer I had and I gain nothing from convincing you of my answers :)</p> </blockquote> <p>I&#39;m not expecting to be convinced. I was hoping to know the benefits from someone who is deeply familiar with both the models. :)</p></pre>TheMerovius: <pre><p>You actually convinced me that the async/await mechanism doesn&#39;t need the separate stack :) I still think the N:M threading model is superior as a programming model though (i.e. significantly less cognitive overhead).</p> <p>But that&#39;s okay. I can certainly agree to disagree on that :)</p></pre>AgentSS: <pre><p>Try reading <a href="http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/" rel="nofollow">http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/</a></p> <p>tl;dr it breaks your world into two non-interoperable parts. As an example, if a third-party library takes an interface with a method that returns without a Promise, and you need to do async work in the implementation of that method, then you&#39;re out of luck.</p></pre>Zeffas: <pre><p>I am not C# guy and not event Go, to be honest, so might be totally wrong here.</p> <p><strong>Two aspects</strong></p> <p><em>IO</em></p> <ul> <li><p>If system IO does not block a thread (non-blocking syscalls) - you can do other work while blocking. GO would need another Goroutine to be created. But its so cheap!</p></li> <li><p>If system IO blocks a thread - need another thread. On both C# and GO (automatically created for other Goroutines). </p></li> </ul> <p><em>Concurrency</em></p> <ul> <li>C# async-await does not improve this in any way I believe. You still need new thread.</li> </ul> <p> </p> <p>In Conclusion, I think concept of Goroutine is simpler - it applies to blocking (async-await) and concurency (threads). So less things to think about. Also it solves problem of Threads being limited resource for computation.</p></pre>gopherookie: <pre><blockquote> <p>But its so cheap!</p> </blockquote> <p>I understand that 2K of stack is cheap enough for most cases, but I want to understand why <code>async...await</code> which has even lower runtime overhead and provides similar benefits to code-readability was not chosen.</p> <blockquote> <p>Concurrency</p> <ul> <li>C# async-await does not improve this in any way I believe. You still need new thread.</li> </ul> </blockquote> <p>I assume you meant parallelism. Yes both <code>async..await</code> and goroutines need OS threads to achieve parallelism.</p></pre>Zeffas: <pre><p>In my opinion, Concurrency and simplicity are the reasons.</p> <blockquote> <p>Yes both async..await and goroutines need OS threads to achieve parallelism.</p> </blockquote> <ul> <li><p>Parallelism (e.g. parallel processing speed): C# and GO are fundamentally equal - limited by OS Threads and CPU cores.</p></li> <li><p>Concurrency (e.g. concurrent jobs): C# is limited by Threads. GO doesn&#39;t have this limitation - 1 Thread = N Goroutines.</p></li> </ul> <p>To me, async-await seems to be better, only very very slightly on non-blocking syscalls - no need to create new Goroutine, but otherwise I think Goroutines are much more powerful.</p></pre>albatr0s: <pre><p>Probably a matter of taste :-)</p> <p><a href="https://www.youtube.com/watch?v=3DtUzH3zoFo" rel="nofollow">https://www.youtube.com/watch?v=3DtUzH3zoFo</a></p></pre>

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

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