A few thoughts on std.allocator

Jakob Ovrum via Digitalmars-d digitalmars-d at puremagic.com
Wed May 13 17:00:39 PDT 2015


On Wednesday, 13 May 2015 at 17:49:38 UTC, Steven Schveighoffer 
wrote:
> OK, then consider that this:
>
> void main()
> {
>    string x;
>    x ~= "hello";
>    x ~= " world";
> }
>
> would require locking. That's unacceptable. Nobody would append 
> with strings if this all required locking for no reason. The 
> runtime currently does NOT lock for this case, it considers 
> immutable and const to be thread-local.

Well, it's necessary because the design of druntime arrays is 
incompatible with D2's type system. Without locking, 
multi-threaded applications that use dynamic array operations 
could easily contain some particularly hard to track concurrency 
bugs.

Simply not doing the locking and hoping that everything is fine 
doesn't sound like a good plan (which I think we agree on, since 
shared(immutable(T)) would solve it).

> No, I think the answer is simpler. Introduce shared(immutable), 
> and then we can distinguish between immutable data that is 
> shared and data that is not shared. It also makes implementing 
> local heaps easier. Shared really is orthogonal to mutability.

Basically, shared(immutable(T)) would only be useful to 
allocators, including arrays because they may need to allocate 
when growing. I don't think it would be useful beyond that; the 
sharedness of immutable data is probably not interesting to any 
other kind of code.

It would make immutable considerably harder to use than it is 
today. shared(immutable(T)) would be implicitly convertible to 
shared(const(T)), but not const(T), which precludes the vast 
majority of mutation-agnostic D code out there today (I have 
never seen shared(const(T)) used in the wild). We would no longer 
be able to do even the simplest things, like passing a path 
string to another thread and use std.file.read on it.

> Here's something even weirder: If you append to y 
> (const(int)[]) as a reference to x (immutable(int)[]), then 
> part of the array is immutable and shareable, and the part that 
> was appended is const and not shareable.

This should be the case for user-defined containers as well as 
long as the element type doesn't have mutable indirection. 
Appending on slices seems to handle this correctly as well, 
rejecting attempts to append a const(T)[] to an immutable(T)[] 
when T has mutable indirection.

> This should work:
>
> void main()
> {
>    auto s = new S;
>    passToOtherThread(&s.b);
> }
>
> Which makes s.a unshared, and s.b shared. But the memory block 
> needs to be shareable.

That is an interesting example. Plain shared has the same problem:

struct S {
     int a;
     shared int b;
}

void main() {
     import std.concurrency;

     auto tid = spawn(() {});
     auto s = new S;
     tid.send(&s.b);
}

Types that contain shared anywhere within the type would have to 
be allocated from a global allocator, and with the current state 
of immutable, the same would have to be done for types that 
contain immutable anywhere within it. Note that it's only the 
case for head-shared/head-immutable; types like immutable(T)[] or 
shared(T)* don't count.


More information about the Digitalmars-d mailing list