6 ms·
No there isn’t. Elixir, and by extension Erlang and the BEAM VM, doesn’t have the concept of stackless coroutines. Task.async/1 (or Task.async/3) at a high lev
by SirGiggles 4y ago
No there isn’t. Elixir, and by extension Erlang and the BEAM VM, doesn’t have the concept of stackless coroutines.
Task.async/1 (or Task.async/3) at a high level just spawns another BEAM process links it to the currently running process, sets up a process monitor, and then runs. Task.await/2 just pulls from the mailbox of the current process for a returned message.
- valenterry 4y agoBut that is exactly what I mean. There is a semantic difference between just running a line of code or running ith with Task.async right? So then there is a distinct concept of sync vs async.
- SirGiggles 4y agoThe BEAM VM doesn't have faculties to accommodate any other form of concurrency other than synchronous, pre-empted green threads. Put simply, there is nothing other than synchronous in Elixir (edit: or Erlang or any other BEAM VM language). The Task module is basically just convenience functions (edit: convenience functions around existing BEAM VM functions for spawning BEAM processes or Erlang threads) meant to facilitate short term execution of, well, tasks. Task.async/3 and Task.await/2 are, from the thousand foot view, the equivalent of: parent = self() spawn(fn -> send(parent, do_something()) end) receive do result -> IO.puts result end In Elixir/Erlang parlance, I have just spawned a thread where the BEAM VM handles both the internal data structures to facilitate concurrent-safe communications between threads and the scheduling in userspace. Put more generally, the mentality of concurrency in Elixir/Erlang is the equivalent as that of traditional pthreads in C.
- valenterry 4y agoI think you are misunderstanding me, so let me ask a simple question: Is it (except for performance) always 100% equivalent whether a calculation is done "locally" or whether it is ran on some other, potentially just spawned, thread and then retrieved from this thread? If your answer is "no it is not always 100% equivalent" then we agree (and I think we do).
- SirGiggles 4y agoNo, they're equivalent. It makes no difference whether I calculate in the current process, spawned a process, started a process via the Supervisor, Task.async, or what not. They are all equivalent in the sense that they use BEAM processes. An Elixir, Erlang, or any other developer using the BEAM VM (or at the very least myself since I don't want to generalize) doesn't make a semantic distinction between spawn and Task.async/3 because there is none. The significant differentiation is whether or not the caller is expecting a returned value, if not then Task.start_link can be used instead. I don't need to worry about function colouring because there is none. I also don't need to worry about async vs Task.run like in C# because in Elixir there is none. To be asynchronous in Elixir/Erlang/etc... is to be synchronous. To equate it to modern backends, we basically have a microservice but every service can directly call each other. I think I also might have been talking around you a bit, the BEAM VM sets itself apart from C#'s async/await or the TPL due to its implementation of the actor model. This video by Sasa Juric can explain better than I can the general overview of the BEAM: [1]. But a rough tl;dw, a BEAM process is to the BEAM, what an OS process is to the OS. We don't define a main function as the process supervisor in BEAM spawns processes we define instead and those are our "entrypoints." [1] https://www.youtube.com/watch?v=JvBT4XBdoUE https://www.youtube.com/watch?v=JvBT4XBdoUE
- valenterry 4y ago> They are all equivalent in the sense that they use BEAM processes. Sure, but that was not my question. 100% equivalent means that there cannot be any observable change in the behaviour of the whole program/application (except for performance). Or in other words: is the sole purpose of the existance of `Thread.async` (or spawn, pick whichever you want) to change performance characteristics? If not, what is the purpose of its existence then?
- SirGiggles 4y ago> Sure, but that was not my question. 100% equivalent means that there cannot be any observable change in the behaviour of the whole program/application (except for performance). In a vacuum (i.e. via spawn/1 or spawn/3) there is no observable change in behaviour throughout the whole system. If they're linked via spawn_link then a child process crashing means the parent process dies with it unless handled in some way. So ultimately, no there isn't observable changes. My logging process doesn't care or even know that my WebSocket process crashed. > Or in other words: is the sole purpose of the existance of `Thread.async` (or spawn, pick whichever you want) to change performance characteristics? If not, what is the purpose of its existence then? As stated before Task.async/3 is just a convenience wrapper around low-level BEAM primitives, there isn't anything special about Task.async/3 that you couldn't do via spawn. The reason being that the BEAM VM schedulers prioritizes overall latency of the system over throughput by enforcing reduction calls (parlance for a function call, but specifically for tracking looping in the BEAM) at around 4000 calls, then it moves the current process back into the queue; repeat ad infinitum. So you don't get massive speedups just spawning a new BEAM process but your independent units of execution still enjoy the same overall latency as before. If you want performance increases in the BEAM you would need to jump to dirty schedulers and NIFs which carry their own dangers.
- throwawaymaths 4y agoEverything in the beam is "low-level" async with implicit yield points at function boundaries. Task.async just gives you a convenient future so you can get the value back, instead of the beam throwing it away (goroutines also throw away the result). You could also write that boilerplate by hand. There are also other nice things that Task.async gives you but that's not relevant to this discussion. IOW, async/await in the BEAM is a high level thing (like goroutines) which is one of two reasonable choices, the other reasonable choice being "what zig did".