This Week in D
May 21, 2017Welcome to This Week in D! Each week, we'll summarize what's been going on in the D community and write brief advice columns to help you get the most out of the D Programming Language.
The D Programming Language is a general purpose programming language that offers modern convenience, modeling power, and native efficiency with a familiar C-style syntax.
This Week in D has an RSS feed.
This Week in D is edited by Adam D. Ruppe. Contact me with any questions, comments, or contributions.
See more at the announce forum.
Contributed by ketmar.
Emulating synchronized(obj) call.
Imagine that you have a nice thread-safe... something. Like file abstraction, for example:
class XFile {
private SomeHiddenClass fileImpl;
void read (void* ptr, size_t count) {
synchronized(fileImpl) {
fileImpl.non_thread_safe_read(ptr, count);
}
}
void write (const(void)* ptr, size_t count) {
synchronized(fileImpl) {
fileImpl.non_thread_safe_write(ptr, count);
}
}
}
And you want to add something like LockedWriter, so you will be able to do:
XFile fl;
...
{
import std.format : formattedWrite;
auto wr = fl.lockedWriter();
// here, fl should be locked with `synchronize(fl.fileImpl)`
formattedWrite(wr, "%s %s!", "hi", "there");
}
Such thing may be useful for formattedWrite(), for example, so your writef() implementation won't be interrupted midway by another thread. But the problem is that synchronized() is the built-in, and you can't separate it to "lock" and "unlock" parts. Likely. But compiler does locking and unlocking under the hood, so it *should* be possible! And it is really possible: you just have to import some hidden druntime functions, and make your hands dirty. Let's do the trick:
extern(C) void _d_monitorenter (Object h) nothrow; // magic import
extern(C) void _d_monitorexit (Object h) nothrow; // magic import
auto lockedWriter (XFile fl) {
static struct LockedWriterImpl {
private XFile fl;
private this (XFile afl) nothow {
_d_monitorenter(fl.fileImpl); //HERE! emulate `synchronized(fl.fileImpl)` enter
}
// postblit: just "enter" one more time, compiler will balance dtor calls
// we are lucky: `synchronized()` mutex is reentrant
this (this) nothow {
_d_monitorenter(fl.fileImpl);
}
~this () nothow {
_d_monitorexit(fl.fileImpl); //HERE! emulate `synchronized(fl.fileImpl)` exit
}
// call underlying thread-unsafe implementation
void put (const(char)[] s...) { fl.fileImpl.write(s.ptr, s.length); }
}
return LockedWriterImpl(fl);
}
What is going on here? Internally, each object has hidden field named "monitor" (this is not a real name, you cannot access the field by this name!). It is used to implement sychronized() locks.
When compiler sees synchronized(obj), it actually generates a code like this:
try {
_d_monitorenter(obj);
...your code here...
} finally {
_d_monitorexit(obj);
}
So we can emulate synchronized() call by doing the very same thing! And that's what LockedWriterImpl does: calling "enter" and "exit" functions directly, by importing them from druntime. As those functions are hidden too, we have to using the trick: we are declaring external C function, and let the linker do the rest.
IMPORTANT NOTE: current LockedWriterImpl is just a sample code. You'd better do at least some error checking there!
To learn more about D and what's happening in D: