On 2012-05-29 23:22:38 +0000, Alex Rønne Petersen <a...@lycus.org> said:

Possibly. I haven't thought that through, but I think that that could work. In fact, it would probably be a good way to ensure safety statically, since a synchronized class tells its user "I take care of synchronization".

More or less.

My biggest gripe about synchronized functions is that calling other functions within them is prone to create deadlocks. Can you see the deadlock in this example?

        synchronized class Node
        {
                private Node[] peers;

                void addPeer(Node peer)    // implicitly synchronized
                {
                        peers ~= peer;
                }

                void poke()    // implicitly synchronized
                {
                        writeln("blip!");
                }

                void pokeNext()    // implicitly synchronized
                {
                        if (!peers.empty)
                                peers.front.poke();
                }
        }

        shared Node node1;
        shared Node node2;

        void pokeThread(Node node)
        {
                node.pokeNext();
        }

        void main()
        {
                // setup
                node1 = new Node;
                node2 = new Node;
                node1.addPeer(node2);
                node2.addPeer(node1);

                // start two threads
                spawn(&pokeThread, node1);
                spawn(&pokeThread, node2);
        }

The correct way to write the pokeNext function would be this, assuming you could remove the implicit synchronization:

                void pokeNext()     // not implicitly synchronized
                {
                        Node next;
                        synchronized (this)
                        {
                                if (!peers.empty)
                                        next = peers.front;
                        }
                        if (next)
                                next.poke();
                }

Here you only synchronize access to the variable, which cannot deadlock in any way. Keeping the lock while calling other functions is dangerous (other functions can take their own lock and thus deadlock) and should be avoided as long as you hold a lock. The easiest way to avoid deadlocks is to never take two locks at the same time. The problem is that implicit synchronized blocks makes it too easy to take many locks at the same time without even noticing, which is deadlock prone.

I think that is a reason enough not to use synchronized classes in the general case. They're fine as long as your member functions (which are implicitly synchronized) only access what is contained in the class, but you must be sure not to call another function that takes another lock, or then you need to be very careful about what those functions do.

(Also, being forced to take locks longer than necessary because the whole function is always synchronized can be a performance problem, but that's a relatively minor issue compared to deadlocks.)

--
Michel Fortin
michel.for...@michelf.com
http://michelf.com/

Reply via email to