Safe concurrency in D
This package implements @safe
easy sharing of mutable data between threads without having
to cast from shared and lock/unlock a mutex. It does so by using scope
and
DIP1000. It was inspired by
Rust's std::sync::Mutex.
The main type is Exclusive!T
which is safely shareable between
threads even if T is not immutable
or shared
. To create one, call
one of gcExclusive
or rcExclusive
with the parameters to the
constructor to create a type T. Passing an already created T would not
be safe since references to it or its internal data might exist
elsewhere.
As the names indicate, gcExclusive
allocates on the GC heap, whereas rcExclusive
uses
RefCounted
from automem. This works automatically
if automem can be imported, which is always the case when automem is listed as a DUB
dependency.
To actually get access to the protected value, use .lock()
(borrow
exists as an alias) to get exclusive access for the current block of
code.
An example (notice that main
is @safe
):
import fearless;
struct Foo {
int i;
}
int* gEvilInt;
void main() @safe {
// create an instance of Exclusive!Foo allocated on the GC heap
auto foo = gcExclusive!Foo(42);
// from now the value inside `foo` can only be used by calling `lock`
{
int* oldIntPtr; // only here to demonstrate scopes, see below
auto xfoo = foo.lock(); // get exclusive access to the data (this locks a mutex)
safeWriteln("i: ", xfoo.i);
xfoo.i = 1;
safeWriteln("i: ", xfoo.i);
// can't escape to a global
static assert(!__traits(compiles, gEvilInt = &xfoo.i));
// ok to assign to a local that lives less
int* intPtr;
static assert(__traits(compiles, intPtr = &xfoo.i));
// not ok to assign to a local that lives longer
static assert(!__traits(compiles, oldIntPtr = &xfoo.i));
}
// Demonstrate sending to another thread and mutating
auto tid = spawn(&func, thisTid);
tid.send(foo);
receiveOnly!Ended;
safeWriteln("i: ", foo.lock.i);
}
struct Ended{}
void func(Tid tid) @safe {
receive(
// ref Exclusive!Foo doesn't compile, use pointer instead
(Exclusive!Foo* m) {
auto xfoo = m.lock;
xfoo.i++;
},
);
tid.send(Ended());
}
void safeWriteln(A...)(auto ref A args) { // for some reason the writelns here are all @system
import std.stdio: writeln;
import std.functional: forward;
() @trusted { writeln(forward!args); }();
}
This program prints:
i: 42
i: 1
i: 2
Please consult the examples directory and/or unit tests for more.