std.concurrency and fibers

Alex Rønne Petersen alex at lycus.org
Thu Oct 4 21:27:02 PDT 2012


On 04-10-2012 22:04, Dmitry Olshansky wrote:
> On 04-Oct-12 15:32, Alex Rønne Petersen wrote:
>> Hi,
>>
>> We currently have std.concurrency as a message-passing mechanism. We
>> encourage people to use it instead of OS threads, which is great.
>> However, what is *not* great is that spawned tasks correspond 1:1 to OS
>> threads. This is not even remotely scalable for Erlang-style
>> concurrency. There's a fairly simple way to fix that: Fibers.
>>
>> The only problem with adding fiber support to std.concurrency is that
>> the interface is just not flexible enough. The current interface is
>> completely and entirely tied to the notion of threads (contrary to what
>> its module description says).
>>
>> Now, I see a number of ways we can fix this:
>>
>> A) We completely get rid of the notion of threads and instead simply
>> speak of 'tasks'. This trivially allows us to use threads, fibers,
>> whatever to back the module. I personally think this is the best way to
>> build a message-passing abstraction because it gives enough transparency
>> to *actually* distribute tasks across machines without things breaking.
>
> Cool, but currently it's a leaky abstraction. For instance if task is
> implemented with fibers static variables will be shared among threads.
> Essentially I think Fibers need TLS (or rather FLS) synced with language
> 'static' keyword. Otherwise the whole TLS by default is a useless chunk
> of machinery.

Yeah, it's a problem all right. But we'll need compiler support for this 
stuff in any case.

Can't help but wonder if it's really worth it. It seems to me like a 
simple AA-like API based on the typeid of data would be better -- as in, 
much more generic -- than trying to teach the compiler and runtime how 
to deal with this stuff.

Think something like this:

struct Data
{
     int foo;
     float bar;
}

void myTask()
{
     auto data = Data(42, 42.42f);

     TaskStore.save(data);

     // work ...

     foo();

     // work ...
}

void foo()
{
     auto data = TaskStore.load!Data();

     // work ...
}

I admit, not as seamless as static variables, but a hell of a lot less 
magical.

>
>> B) We make the module capable of backing tasks with both  threads and
>> fibers, and expose an interface that allows the user to choose what kind
>> of task is spawned. I'm *not* convinced this is a good approach because
>> it's extremely error-prone (imagine doing a thread-based receive inside
>> a fiber-based task!).
> Bleh.
>
>> C) We just swap out threads with fibers and document that the module
>> uses fibers. See my comments in A for why I'm not sure this is a good
>> idea.
> Seems a lot like A but with task defined to be a fiber. I'd prefer this.
> However then it needs a user-defined policy for distributing fibers
> across real threads (pools). Btw A is full of this too.

By choosing C we effectively give up any hope of distributed tasks and 
especially if we have a scheduler API. Is that really a good idea in 
this day and age?

>
>> All of these are going to break code in one way or another - that's
>> unavoidable. But we really need to make std.concurrency grow up; other
>> languages (Erlang, Rust, Go, ...) have had micro-threads (in some form)
>> for years, and if we want D to be seriously usable for large-scale
>> concurrency, we need to have them too.
>>
>> Thoughts? Other ideas?
>>
> +1
>


-- 
Alex Rønne Petersen
alex at lycus.org
http://lycus.org


More information about the Digitalmars-d mailing list