Welcome 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: