synchronized class bugs?

Gregor Mückl gregormueckl at gmx.de
Wed Apr 8 22:50:07 UTC 2020


On Wednesday, 8 April 2020 at 13:43:08 UTC, Steven Schveighoffer 
wrote:
> This is why we are moving to REQUIRING casting away shared to 
> read/write data -- you have to do it anyway.
>

OK, I understand the rationale, even though I don't like the 
solution very much. You shouldn't really use atomic ops when 
locks on the affected variables are held. That's bad practice.


>> Bug number 2: even when casting away shared, the end result is 
>> wrong when using multiple threads. Thus, the monitor locking 
>> code in druntime cannot be correct. It does have an effect, 
>> though. Omitting synchronized on Shared results in values that 
>> are wide off the mark. Including it results in small 
>> differences. This suggests that a memory fence might be 
>> missing somewhere.
>> 
>> The observed reality contradicts the documentation in many 
>> ways and is inconsistent with itself. What is going on here?
>
> synchronized classes call _d_monitorenter and _d_monitorexit, 
> which lock OS primitives (and initialize the monitor if 
> necessary). These have not been touched in years, and depend on 
> the OS mutex which should be rock-solid. Looking at the 
> history, it looks like the last significant change was 
> https://github.com/dlang/druntime/commit/70b3536ddd06e8e1afb269b88ed3298ec03b8798#diff-d609308f6ce1b06db16e214b30db6f74 in 2015.
>

The calls to _d_monitorenter and _d_monitorexit are in the 
generated code and I've looked up their implementation before 
posting to make sure I have correct understanding.

I would also assume mutexes to be rock solid. This is why I am in 
utter disbelief about the actual behavior of the toy program (see 
code at the end).

> I've definitely depended on synchronized functions in the past, 
> and haven't had issues, but it was in the far past.
>
> The only place I can reasonably assume that there might be an 
> issue is for the initialization.
>
> To test this, try initializing the lock FIRST before spawning 
> multiple threads (just call one of the functions), and see if 
> it helps. If so, it might be a bug in the initialization 
> routines.
>

I added such function calls and that didn't change anything.

> Also if you find an issue, it may be specific to your OS.
>

Well, as I've mentioned before, I've reproduced this on Linux and 
Windows with dmd, ldc and gdc. So it's not OS specific, or at 
least not entirely.

> -Steve

The the full program code is below the line. You can run it on 
run.dlang.org to reproduce the problem.

----

import core.thread;

import std.stdio;

version = Sync;

version(Sync) {
     pragma(msg, "synchronized");

     synchronized class Shared {
     public:
         void increment(int value) {
             (cast(Shared) this).x += value;
         }

         void decrement(int value) {
             (cast(Shared) this).x -= value;
         }

         shared int get() { return x; }

     private:
         int x;
     }
} else {
     pragma(msg, "non-synchronized");

     class Shared {
     public:
         shared void increment(int value) {
             (cast(Shared) this).x += value;
         }

         shared void decrement(int value) {
             (cast(Shared) this).x -= value;
         }

         shared int get() { return x; }

     private:
         int x;
     }
}

void main()
{
     shared Shared s = new shared Shared();

     s.increment(1);
     s.decrement(1);
     writeln(s.get());

     Thread[] threads = new Thread[10];
     for(int i = 0; i < threads.length; i++) {
         threads[i] = new Thread({
             for(int j = 0; j < 1_000_000; j++) {
                 s.increment(i);
             }
             for(int j = 0; j < 1_000_000; j++) {
                 s.decrement(i);
             }
         });
         threads[i].start();
     }

     for(int i = 0; i < threads.length; i++) {
         threads[i].join();
     }

     writeln(s.get());
}



More information about the Digitalmars-d mailing list