3 ms·
> Turning sync into async is harder in any language. Elixir's Task module (in the stdlib): future = Task.async(fn -> do_something_here end)
by dnautics 7y ago
> Turning sync into async is harder in any language.
Elixir's Task module (in the stdlib):
future = Task.async(fn ->
do_something_here
end)
...do_other_things...
result = Task.await(future, timeout)
Mixing it with the Enum library makes concurrency dead-simple (got I a junior dev dispatching concurrent tasks in scripts with confidence), at the expense of an ugly nested double lambda.
some_list_of_values
|> Enum.map(fn value ->
Task(fn -> do_something_with(value) end)
end)
|> Enum.map(&Task.await(&1, timeout))
- dymk 7y agoThis incurs runtime overhead and boilerplate, so while it’s not as hard as other languages, it’s still harder.
- dnautics 7y agolike what, a few microseconds? What are you doing where you're awaiting for things in parallel where that matters? HPC? We're dispatching things that take on the order of minutes. Typically a local network request has 10-20 milliseconds of latency on our office LAN, so whatever. Clean and comprehensible code with very little boilerplate is more important when I'm reviewing my junior's code.
- nemothekid 7y agoWell, thats a hard sell for Rust because it specifically advertises itself as a C++ replacement which means no overhead or runtime.
- dnautics 7y agoI think if you're striving for that, then a bit of complexity is warranted. Not everything has to be simple, and async is hard to do correctly without the correct abstractions. Honestly, though I was hoping Rust would go with the Actix way of doing things, but that's fine. You don't have to use Rust's async.
- zanny 7y agoDoes Elixer overload IO operations to be async in async contexts? Because that is largely why you cannot just wrap sync code in an async block and call it a day - once it hits a system call the thread is paused but the scheduler cannot tell that it should be dequeued. This is largely why Python async took so long to mature, because so much inbuilt functionality was making IO operations transparently using core sync impls that locked up any async executor.
- dnautics 7y agoI'm still relatively new to the erlang vm so some of the details here might be wrong, if someone wants to correct me, please it's welcomed. Console IO operations are actually message call to a "global group leader" which performs the operation, so they are async (and atomic). This can sometimes be confusing if an operation (such as logging) has a bunch of middlemen with an IO operation as a side effect. It's worth the atomicity, though, so none of your IO calls are interrupted by another IO call. Also, if you run a command on a remote node which dispatches IO as part of its own process, the IO will be forwarded back to its group leader (which is on your local node), which is useful for introspecting into another VM. Disk IO is also different; each open file descriptor effectively gets its own "thread" that you send IO messages to. There are ways to bind a file descriptor directly to your current "thread", but you "have to be more careful when you do that" - you do that if performance is more important (and I have done this, it's not terrible if you are careful). Network IO is also different; the erlang VM kind of has its own network stack, if you will, but you can set up a socket to be its own "thread" or you can bind a socket into a thread so that network packets get turned into erlang messages. Handling blocking is all done for you by the VM, which is preemptive and tries to give threads fair share of the VM time. When people say that programming the erlang VM is like doing everything in its own os, they aren't kidding. Except unlike linux, where your communications are basically limited, you get to interact with your processes via structured data types with coherent language (and also IPC calls are way cheaper than OS processes). > Does Elixer overload IO operations to be async in async contexts? Maybe the right way to answer this is: When in Elixir, presume everything is async.
- nimish 7y agoElixir Tasks act closer to a very lightweight threadpool dispatch, rather than the coroutine style of async/await in other languages. An Elixir task doesn't, iirc, share memory with other tasks and won't block if you make it spin. This makes it a hell of a lot easier to reason about.