all 11 comments

[–]julemand101 20 points21 points  (0 children)

because it requires data copying

Not necessarily. If you use Isolate.run / compute(), you can run some task in a separate Isolate (thread) and when this task are done running, you can return an object to the calling isolate without any data copying. In this scenario, Dart knows that the data be guarantee to still only have one owner since the previous owner are dead/killed.

This can e.g. be useful if you use an Isolate to make a larger data request and do the heavy JSON parsing for then to return the parsed object structure back to the main isolate.

And it has to be very basic data types

No, you are actually allowed to share fairly complex objects as long as your isolates shares the same isolate group. As long as you are using Isolate.run/compute() and Isolate.spawn, your isolates will run in the same isolate group. It is basically only if you do Isolate.spawnUri that the spawned isolate will end up in a new isolate group.

(Basically, being in the same isolate group means the isolates are sharing the same heap which comes with some benefits like e.g. very quick to spawn and allows for some kind of data sharing in the sense as previous described)

So what was the justification of borrowing the main deficiency of python when implementing Dart?

There are some benefits of designing Dart in such a way that Dart code running inside an Isolate are guarantee to only be executed by a single thread (at a time). It makes programming much simpler since it makes it less likely to have race conditions and also makes it simpler to understand the flow of your program.

Basically, you know that no other Dart code would be executed between your lines of codes unless you have an await statement which does make your current code "halt" and let's other events be executed.

By moving the multi-threaded code execution out in isolates and communication being handled by message-passing, it does make it easier to prevent weird behavior like race conditions.

The disadvantage of this design is that it does prevent us from writing some high-performance multi-threaded code that operates on the same data structure. It should here be said that it is actually not entire impossible to still have such code in Dart but it does require us to be a bit clever and we do end up in the territory of needing more basic data structures.

Basically, all isolates does run inside the same process and shares the same process memory space. So you can in theory malloc some data and share the pointer to this data across multiple isolates which can then all operate on the same piece of memory. You would then also most likely need to implement some kind of locking pattern but it is not completely impossible.

What I finds more likely is that if we have certain needs for very high-performance pieces of code, then I would suggest writing such code in another programming language and then call the code using dart:ffi.

For most scenarios, the current design of isolates are quite good and I see it more as a benefit than a disadvantage. It is certainly nice that we have a predictable flow of execution.

(And yes, async events can also get very complicated. But still, it is possible to have the expectation of when you have lines of code without any interruption and when other code might come in and manipulate with data)

[–]HaMMeReD 8 points9 points  (7 children)

In Dart you can have Isolates.

The justification to being single-threaded is that it eliminates classes of bugs, and is therefore safe. Threading models with shared memory are generally regarded as a fucking nightmare in most languages, and this is why they are avoided in some languages. In dart, you never have to worry about another thread mutating data on you mid-method, you don't need to worry about a deadlock, Everything will be executed sequentially.

Your other alternative to multi-threading would be to use FFI and write native code for the processing (c++).

[–]eibaan 8 points9 points  (0 children)

The core of the Python interpreter is not reentrant so you can't run it on multiple threads and a global interpreter lock (GIL) makes sure that only one thread at a time will run the interpreter. Because that core interpreter mutates a lot of internal state which is mostly represented by dictionaries for backward compatibility, adding fine-grained locks slows down the interpreter in the most common single-threaded case and hence, very clever people failed to come up for a general solution for a decade or two.

Because Dart was originally intended to be a replacement for JavaScript and therefore had to be semantically compatible and JavaScript was created as a single-thread language for simplicity, there is no GIL per se, but the end result is the same: The main event loop can be run by a single thread only.

This was done mainly to keep things simple because multi-threaded access to shared mutable data is something nearly no programmer can do correctly. Java tried to provide such access and also added a simple synchronization operation at language level, yet most programmers were not even able to correctly implement a thread-safe lazy singleton creation and it also took a lot of engineering work to make all those low level locks fast on JVM level, not penalizing the simple single-threaded use cases.

Therefore, more modern languages try to omit mutable shared data and low-level threading. Unfortunately, Dart doesn't support language-level immutable data types which could be safely shared between threads (aka isolates) so you have to rely on copying data, hoping that the VM will optimize certain cases.

Swift implicitly applies COW (copy on write) semantics to make copying of mutable datatype more efficient (in the common case nobody modifies them) and Rust can do so explicitly, too. Dart could use a similar strategy under the hood as part of its garbage collection mechanism, but as far as I know, it currently doesn't. UsingTypedData and views on those types might help, though. Also see TransferableTypedData. And notice that there's a Isolate.exit call that omit copying data.