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

90 views Asked by At

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.

2

There are 2 answers

8
Theodor Zoulias On BEST ANSWER

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);
    }

});
8
Enigmativity On

I do like the Rx or System.Reactive approach for doing this. It's quite neat.

Observable
    .Range(0, totalSIM)
    .Select(x => Observable.Start(() => { /* do stuff */ }))
    .Merge(Environment.ProcessorCount)
    .Select((_, n) => n + 1)
    .Where(x => x % 200 == 0)
    .Subscribe(n => _printConcurrent(n));