Double-Checked Locking Considered Broken
The spectre of double-checked locking turned up again recently, this time on the Cairo mailing list.
The temptation to double-checked locking often turns up when you’ve got code like this:
global boolean flag = false;
...
if (!flag) {
to_be_done_once_and_only_once();
flag = true;
}
...
That works well enough when you’ve only got a single thread involved. However, it breaks handily when you introduce more. They stomp on each other’s toes.
The solution is to introduce a lock:
global boolean flag = false;
global mutex lock;
...
acquire(lock);
if (!flag) {
to_be_done_once_and_only_once();
flag = true;
}
release(lock);
...
However, this is the point where many people get greedy. Why pay the overhead for that one lock? If we tentatively check the flag before acquiring the lock, we can often skip the lock and the real test, can’t we?
global boolean flag = false;
global mutex lock;
...
if (!flag) {
acquire(lock);
if (!flag) {
to_be_done_once_and_only_once();
flag = true;
}
release(lock);
}
...
Well, no. You’ve just introduced a nasty bug. This is the double-checked locking antipattern of lore. You can get away with a construct like this with some combinations of compiler, language, and architecture, but not others. For the unlucky, every once and a while the protected function will get called more than once. Sometimes. When the moon is just right.
Since this antipattern typically appears in infrequently-called initialization code, you may never experience the failure yourself. Bugs get closed as “unable to reproduce”. At least if it had been in some tightly-wound inner loop, you might have noticed the occasional weird behavior yourself simply because it was called so frequently.
But infrequently-called initialization code isn’t a tightly-wound inner loop, is it? Perhaps the “optmization” was a bit premature.
The take-away lesson?
Writing correct explicitly multithreaded code is a Hard Problem™ as it is. Don’t get greedy, and especially don’t play creative games with locking unless you’re the bastard spawn of Linus Torvalds, Bill Pugh, and an IBM research division or two.