Preface to the Preface
I have been reading into async, and Nim's implementation of async. @Dom96's book is amazing on this subject btw. much cudos... I have also been going down the rabbit hole of reading Joe Duffy's amazing blog and the Midori project (http://joeduffyblog.com/2015/11/19/asynchronous-everything/)
Preface
Async and threads can go together like peanut butter and jelly, but sadly, the Nim implementations are currently incomplete.
Specifically: We have two communication primitives for Threads today
But neither one has async support yet. This is sad.
On to the Idea already
My idea is to attempt to "kill two birds with one stone" so to speak.
Nothing special here. The polling just needs to be integrated into asyncDispatch, and the appropriate async apis exposed for channels.
A FlowVar is essentially a Future that is specialized to wait for another thread. A Channel is a way to send and receive values across threads. In other words, a FlowVar is an async channel that is single use (one send and receive).
A FlowVar can essentially become a template / macro that expands to:
Why I like this idea
Notes
Questions
What does the community think about this?
Thanks in advance for any feedback and questions :-)
the blog link is nice, thank you very much. Its long but worth read:
C#’s implementation of async/await is entirely a front-end compiler trick. If you’ve ever run ildasm on the resulting assembly, you know: it lifts captured variables into fields on an object, rewrites the method’s body into a state machine, and uses Task’s continuation passing machinery to keep the iterator-like object advancing through the states.
Nim´s one is more like in node.js (I think). If you process large chunks at once or a endless loop is placed inside you will break your entire system...
And yes, the Nim channel`s impl is "very fat". Thats the reason I did the messaging within my timperpool impl with Locks and Conditions.
Basically you can do everything with Locks and Conditions but thats a little bit "low level".
So the question could be: whats the audience/usecase for async/await - feature vs threads? new Nim users could be confused about that (like me).
A vocal portion of the community has not bought into newruntime, or is keeping the GC for other reasons, but everyone has accepted async.
This. 👑
Thank you for writing this. I've only skimmed it, but what I've read makes sense in general. Keep in mind that we already have issues for most things that you've mentioned (including the async stream rewrite) so look them up and give them a read, there might be some context there that's useful. If you could tackle all these issues that would be awesome, I'm happy to review anything you write.
Just for reference, the main thing that I think we need delivered is the ability to await a FlowVar. You can already poll a FlowVar which allows you to sort of await it in a poor-man's fashion. await (spawn foo()) should Just Work (TM) and work in an efficient manner, i.e. the waiting for the FlowVar should be done in epoll/kqueue/IOCP.
Hope that makes sense.
Nim´s one is more like in node.js (I think). If you process large chunks at once or a endless loop is placed inside you will break your entire system...
No idea what you mean here exactly, but it sounds wrong.
I meant that the async implementation of nodejs compared to the c# ones seems to be different: https://nodejs.org/en/docs/guides/dont-block-the-event-loop/ and http://www.garethrepton.com/AsyncAwait/
hope I clarified that :-)
@Mikra, It's nice to find another Joe Duffy fan. His posts are long, but always super interesting!
Actually, Nim's implementation of async is actually very similar to the C# version, which is very similar to the Node version, which is how old Python did it.
TLDR; they all read the same papers and did similar things. Our async is also just a front end compiler trick (that is just another phrase for macro).
We specifically implement our async very similar to how Python did it before Python 2.5( @see: https://en.wikipedia.org/wiki/Coroutine#Comparison_with_generators)
But this trick is used in many languages that have generators, but can't manipulate the stack at a low level (including C# and Node.js).
Generator macros is a very elegant hack, but, It's not the most efficient way to implement async. Native runtime support with split stacks is better, as Joe Duffy describes in his post. This is what newer Python implementations and Midori do.
This would be hard to do in Nim because we don't have a lot of control over the stack (since we compile to C). It's called "Duff's device".
@See here for more details: https://en.wikipedia.org/wiki/Coroutine#Implementations_for_C and here: https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
Nim could probably do it (we support inline assembly), but it would be ugly and not very cross platform.
So the question could be: whats the audience/usecase for async/await - feature vs threads? new Nim users could be confused about that (like me).
I don´t think it´s a good idea to "marry" both - but that´s just my humble 2cts..
Sorry to be cheeky, but, New Nim users (and programmers in general) need to learn the difference between concurrency and parallelism. :-P
This is a common confusion. Async and Threads are different design patterns that solve different problems. The problems they solve look similar on the surface, which is why people get confused.
An example of where you would use threads: "I need to sort a large array, so I am going have a bunch of cores each sort a small piece of that array, all at the same time, so it finishes faster."
An example of Async is: "I need to read this file. Instead of putting the whole thread to sleep to wait for the hard drive to give me the file, I'm going to pass off control to another function to do some work, then just wake me up when the file is ready."
The thing is, (usually for advanced cases), you may want both patterns. A web browser is a good example of this:
You need the two threads to talk to each other though. In this case, you want a nice API for your GUI code to talk to the render thread, (which might be busy parsing, so it can't talk right now).
The GUI thread is async, it doesn't want to stop and wait for the other thread (and make your browser unresponsive).
An async channel solves this problem. You send the other thread a message on the channel, then await, and let some other function take over and do other work (like respond to the user typing), then wake up again when the other thread finally sends a response back.
Just for reference, the main thing that I think we need delivered is the ability to await a FlowVar. You can already poll a FlowVar which allows you to sort of await it in a poor-man's fashion. await (spawn foo()) should Just Work (TM) and work in an efficient manner, i.e. the waiting for the FlowVar should be done in epoll/kqueue/IOCP.
Yeah. We are on the same page. I should clarify:
I was looking through the code for both Channels and FlowVar, and I noticed that they are very similar, and both require similar changes to be async (basically adding the wait to epoll)
That got me thinking, that semantically, Channels and FlowVar are very similar. So why do the work twice? Channels are the more general abstraction, so just make those work, and make sugar the for rest.
The async stream rewrite was just me thinking off the top of my head. I haven't done as much research on that. I will look into the issues there, but that seems like a much lower priority.
As @araq says, I think I just need to write some code now. I just wanted to get some buy in before I started anything.
This is a common confusion.
Threads: Literally running two different pieces of code in parallel at the same time, on two physically different CPU cores.
Are you sure your definition is exact? I have the feeling that threads often mean concurrency, which is not parallel. See
But I am confused by the terms often :-)
No, my definition is not exact, but I hope it was good enough to get the point across.
Technically, in the most pedantic sense, a "thread" is a unit of execution (a stack, a set of registers, and an instruction pointer).
By that technical definition, a "thread" is a unit of concurrency, you are correct. "In the old days", it was known as time slicing.
It is what is known as "pre-emptive concurrency". In other words, your program has no say in the matter. The Operating system just decides to pause your program and run another one. The operating system can decide to run your program on one physical CPU core, and then decide to move it to a different physical CPU core if it wants.
Async is what is known as "cooperative concurrency". Your program, not the OS, gets to decide when it pauses and what code runs next. (via the "await" keyword.)
Neither one is "parallel" in a strict sense. Technically Parallel is a physical feature of hardware. There is no such thing as parallel software, there is only parallel hardware.
That being said, when the common newbie or intermediate programmer refers to "threading", they usually are referring to using multiple cores on their CPU, i.e. parallelism.
As a user program, you are actually not allowed to directly execute code on another core. You are only allowed to create a "thread", which the OS will then decide to run when and where it wants (usually on another core, but not guaranteed.)
My point is that these two concepts complement each other very well, and we should have good API's for making them work together.
Sorry to be cheeky, but, New Nim users (and programmers in general) need to learn the difference between concurrency and parallelism. :-P
Thank you! I'll be honest and say that I've actually learned this myself when I was writing Nim in Action. This is also why I spend quite a bit of the book explaining this difference, because it is important, and nowadays I'm really surprised just how many programmers use these terms interchangeably.
The underlying mechanism needed for this is here: https://github.com/nim-lang/Nim/pull/12372
It contains a race condition that needs to be fixed, and then flowVar refactored to use this mechanism.
Unfortunately, Progress has stalled due to several factors including my lack of time.