language icon indicating copy to clipboard operation
language copied to clipboard

Shared Memory Multithreading

Open mraleph opened this issue 2 years ago • 5 comments

Initial proposal attempting to introduce shared memory multithreading.

/cc @leafpetersen @lrhn @eernstg @munificent /cc @a-siva @alexmarkov @mkustermann @dcharkes @liamappelbe @chinmaygarde /fyi @syg @kevmoo

mraleph avatar Dec 21 '23 13:12 mraleph

Hello!

I was reading the proposal and kinda resonated with me the issue of not being able to do parallel operations that are thread safe, because they're not really operating on the same data in the same class, because the class that someone created is not shareable, so I believe that a better way would be to have concepts in the language that would be similar to Rust's Send and Sync.

I am aware that currently the Dart language does not statically track the usage of objects to be able to have the compiler implement Send automatically, but it would make the easiest transition as it would enable a lot of usecases with no changes to library code. About sync, well, that's a little bit harder. I just wanted to point out that I am not a heavy user of Rust, so if anyone disagrees with it, please enlighten me on why.

Piero512 avatar Jan 03 '24 15:01 Piero512

Thanks to everyone for the initial feedback. I have done some major rewrite removing things which complicated the proposal (e.g. treatment of generics and functions) from the proposal. I have added a section which tries to better explain the motivation for suggesting shared memory multithreading as well as a section which covers various popular programming languages to illustrate how they approach the same problem.

Please take another look. I think this is getting to the point where I will take another round of comments to clean the structure and writing and then I probably want to land this PR and take discussion to the issues.

cc @aam @brianquinlan @dcharkes @kevmoo @liamappelbe @loic-sharma @lrhn @yjbanov

mraleph avatar Jan 25 '24 19:01 mraleph

This is nice. I like the comcept of the executor. Have you considered how it would be intended to be used? I was thinking something in terms of say, async communication from regular isolate, and shared isolate state.

To use your miniaudio example, say I'm running a resampling operation from a microphone,so I hook up dart:ffi to generate my function pointer to implement my data callback, where I read from mic, do some resampling (in Dart) and return, and use some integers to keep a count of how many frames I have converted (for reasons).

I imagine keeping track of this operation and dynamically modifying it from regular isolated world, would be easiest if I have a two way communication with that code, so say I build it using dual sendports. On the callback of the sendport, from the shared isolate, which must run in an executor, I atomically fetch the stored integers (because I'm assuming that the executor and the callback provided to miniaudio run in different threads), and report back through the sendport of the main isolate after receiving the report message from the main isolate.

For that example, the executor would need to be given the shared isolate (or isolates) to run in the same context as the ffi callback.

Of course an easier design would be to just have the ffi callback send the updated count to the sendport, but this is just for the sake of argument.

What do you think of this example? Do you see it working that way?

Piero512 avatar Jan 25 '24 20:01 Piero512

Thank you for this proposal; it will be crucial for achieving performant parallelism in Dart.

I would like to suggest an additional level of Shareable objects. Since a Shareable already represents the scope of memory/data that can be shared between isolates/threads, it would be beneficial to also facilitate a synchronization model for manipulating that shared data:


/// A [Shareable] object with synchronized manipulation of internal data.
/// Any call to a method/getter/setter of a [ShareableSynced] object will
/// occur in a synchronized way, locking the object, executing the
/// method/getter/setter, and then unlocking the object. This ensures
/// that the internal data of the [ShareableSynced] is manipulated in a
/// thread-safe manner.
///
/// All internal fields of a [ShareableSynced] object need to be private,
/// isolating the internal data from unsafe manipulation.
///
/// Methods/getters can only return primitive types, immutable types or
/// [Shareable] objects, avoiding any leakage of internal objects.
/// 
/// Methods, getters, and setters will always perform a non-reentrant
/// synchronization over the object.
/// 
/// Sub [ShareableSynced] objects will also trigger their own synchronization.
///
abstract interface class ShareableSynced extends Shareable {
}

class Counter implements ShareableSynced {
  
  int _n = 0;
  
  int get() => _n;
  
  int increment() => ++_n;
  
}

void main() async {
  
  final counter = Counter();
  
  print(counter.get()); // 0

  var isolateRun1 = Isolate.run(() {
    print(counter.increment()); // 1 or 2
  });

  var isolateRun2 = Isolate.run(() {
    print(counter.increment()); // 1 or 2
  });

  await Future.wait([isolateRun1,isolateRun2]);

  print(counter.get()); // always 2
  
}

gmpassos avatar Feb 01 '24 19:02 gmpassos

Please, don't ignore the ShareableSynced idea.

Best regards.

gmpassos avatar Feb 13 '24 00:02 gmpassos

I have some small tweaks here and there and just going to land this.

mraleph avatar Apr 09 '24 10:04 mraleph