Some Issues With Synchronized

Andrew Wiley wiley.andrew.j at gmail.com
Thu Dec 22 01:47:31 PST 2011


I'm having some issues getting the synchronized modifier to work like
a synchronized block. Sorry this code example is long, but here's
three dummy implementations of a Condition-based work queue:

---import std.stdio;
import core.sync.mutex;
import core.sync.condition;
import core.thread;

synchronized class Queue1 {
private:
    bool _work;
    Condition _cond;
public:
    this() {
        auto lock = new Mutex(this); // initialize the monitor for
this object so we can use the same lock in the Condition
        _cond = new Condition(lock);
        _work = false;
    }
    void doWork() {
        while(!_work) (cast()_cond).wait();
        _work = false;
        writeln("did work");
        return;
    }
    void addWork() {
        _work = true;
        (cast()_cond).notify();
        writeln("added work");
    }
}

class Queue2 {
private:
    bool _work;
    Condition _cond;
public:
    this() {
        auto lock = new Mutex(this); // again, initialize the monitor
explicitly so we can reuse the lock
        _cond = new Condition(lock);
        _work = false;
    }
    synchronized void doWork() {
        while(!_work) (cast()_cond).wait();
        _work = false;
        writeln("did work");
        return;
    }
    synchronized void addWork() {
        _work = true;
        (cast()_cond).notify();
        writeln("added work");
    }
}

class Queue3 {
private:
    bool _work;
    Condition _cond;
    Mutex _lock;
public:
    this() {
        _lock = new Mutex(this); // make our own lock and ignore the monitor
        _cond = new Condition(_lock);
        _work = false;
    }
    void doWork() shared {
        synchronized(_lock) {
            while(!_work) (cast()_cond).wait();
            _work = false;
            writeln("did work");
            return;
        }
    }
    void addWork() shared {
        synchronized(_lock) {
            _work = true;
            (cast()_cond).notify();
            writeln("added work");
       }
    }
}

void main() {
    version(Q1) auto queue = new shared(Queue1)();
    version(Q2) auto queue = new shared(Queue2)();
    version(Q3) auto queue = new shared(Queue3)();
    auto thread = new Thread({
        while(true) queue.doWork();
        });
    thread.start();
    while(true) queue.addWork();
}
---

As you can see, this program starts a producer thread and a consumer
thread, and it should endlessly print "did work" and "added work".
Because the consumer has to wake up from the condition variable
periodically, the producer tends to print far more often.

So first, according to the D spec, I believe these three queues should
behave identically. Is that true?

Here's what happens:
Queue1:
  DMD: crash
  GDC: crash
    Both print errors that look like this:
core.sync.exception.SyncException at src/core/sync/mutex.d(159): Unable
to unlock mutex
    From GDC's stack trace, it looks like the constructor call is
being synchronized, so it crashes when I swap out the lock halfway
through the constructor. Do we really need to synchronize constructor
calls? I've never heard of another language that does this.

Queue2:
  DMD: works
  GDC: deadlock
    Unless someone can see an issue here, I guess this is a GDC bug
and should be handled separately.

Queue3:
  DMD: works
  GDC: works
    This is about the most problematic way to do locking, but it does
seem to work reliably.


More information about the Digitalmars-d mailing list