Search code examples
c#multithreadinglocking.net-6.0parallel.for

How to lock Parallel.For every n iterations instead every time?


I 've the following code:

int counter = 1;
var lockTarget = new object();
Parallel.For(1, totalSIM,  i => {

    /* do some stuff  */

    lock(lockTarget) {
        _printConcurrent(counter++);
    }

}); 

and I need to call _printConcurrent every ~200 times, not every time. I thought about do a MOD (%) for counter, but I can't understand how to use the condition. I enclosed the lock in an if but I can't use the counter so I'm in a logic loop instead the code :S

Any help will be appreciated.

_printConcurrent is only an output of the process, I sacrifice some performance for monitoring data.


Solution

  • You could increment atomically the counter without locking, with the Interlocked.Increment method, and enter the lock only if the result of the atomic increment is divisible by 200:

    int counter = 0;
    ParallelOptions options = new() { MaxDegreeOfParallelism = Environment.ProcessorCount };
    Parallel.For(0, totalSIM, options, i => {
    
        /* do some stuff */
    
        int current = Interlocked.Increment(ref counter);
        if (current % 200 == 0)
        {
            lock (options) _printConcurrent(current);
        }
    
    });
    

    This way the _printConcurrent will be called once every 200 iterations, without blocking the rest 199 iterations.


    Update: The above approach has a minor flaw. It doesn't guarantee that the _printConcurrent will be called sequentially with an incremented argument. For example it is theoretically possible that the _printConcurrent(400) will be called before the _printConcurrent(200). That's because the operating system can suspend any thread at any time for a duration of around 10-30 milliseconds (demo), so a thread could be suspended immediately after the Interlocked.Increment line, and lose the race to enter the lock by a non-suspended thread. In case this is a problem, you can solve it be throwing a second counter in the mix, that is incremented only inside the lock:

    int counter1 = 0;
    int counter2 = 0;
    const int step = 200;
    ParallelOptions options = new() { MaxDegreeOfParallelism = Environment.ProcessorCount };
    Parallel.For(0, totalSIM, options, i => {
    
        /* do some stuff */
    
        if (Interlocked.Increment(ref counter1) % step == 0)
        {
            lock (options) _printConcurrent(counter2 += step);
        }
    
    });