2

I thought that my problem is the following -- how to prepare for parallel execution?

My current code resemble this pattern:

  var worker = new Worker();
  while (true)
    worker.RandomTest();

RandomTest is mostly CPU-bound. On rare occasions, workers will write to a file (after obtaining a lock on it), but this is so infrequent that I/O delays shouldn't need to be considered.

I would like to run this in parallel, I could use Parallel.For and use some big number as an upper limit (instead while), but I have problems how to prepare for such execution, because I don't want to create worker at each iteration.

To prepare I would need to know parallel pool size in advance and also inside loop the index of the thread/task/job so I would know how to associate current execution path with the worker.


As it turns out my problem is more fundamental. I couldn't figure out how to make preparation, so since I have endless loop I though why not:

  Parallel.Loop(0,1000,(i,_) => {
    var worker = new Worker();
    while (true)
      worker.RandomTest();
  }

Till now I assumed the launched executions will be in sane limits in comparison to available CPU cores. But no -- Parallel creates new iterations like crazy, so already created jobs are basically stalled because of the incoming flood of the new ones.

Of course I can hardcode the fixed number how many parallel jobs to run, but then the responsibility of figuring out how much is not too much and not too little is on me.

I know how to put a fixed number, but how to put a good number? I.e. when I run program on different machine or in different conditions (for example with CPU-demanding process in the background)?

Here Worker is not thread safe type, thus I create as many worker as there are jobs to do.

outis
  • 75,655
  • 22
  • 151
  • 221
greenoldman
  • 16,895
  • 26
  • 119
  • 185
  • 1
    Does RandomTest block on IO? The thread pool will continue to create threads provided that occupancy is not 100%, in other words, once all processors are occupied with runnable threads, the thread pool will stop creating new ones. But if the threads become blocked on IO or wait for some synchronization object (i.e. become non _runnable_) then the thread pool will create another thread to try to achieve 100% processor occupancy (which may consume a lot of memory with thread stacks, but is technically efficient in terms of processing.) – Wyck Dec 16 '22 at 20:44
  • @Wyck, RandomTest is CPU-bound. The code also includes one lock when writing error dump (but it is rare, and this is basic lock+write and that's it -- each instance does not wait for the other). – greenoldman Dec 16 '22 at 21:03
  • while(true) is still an endless loop. How does your RandomTest() end? – Paul Palmpje Dec 16 '22 at 22:06
  • @PaulPalmpje manually aborting the program. `RandomTest` is random bug hunter, whenever it finds a bug, it saves input data and then continue. So after a few hours I should have some new cases to work on. – greenoldman Dec 17 '22 at 05:09
  • 1
    Related: [What's the best way of achieving a parallel infinite Loop?](https://stackoverflow.com/questions/8671771/whats-the-best-way-of-achieving-a-parallel-infinite-loop) – Theodor Zoulias Dec 17 '22 at 13:32

1 Answers1

1

One way to solve this problem is to start a number of threads equal to the number of cores, and do an infinite while loop in each thread:

for (int i = 0; i < Environment.ProcessorCount; i++)
{
    _ = new Thread(() =>
    {
        Worker worker = new();
        while (true) worker.RandomTest();
    });
}

These threads are foreground threads, so they'll keep the program alive even after exiting the Main method. In case any of the loops fail, the whole program will crash immediately with an unhandled exception. This primitive behavior might be closer to what you want to do, than the sophisticated Parallel class with its obscure partitioners.

Regarding the optimal degree of parallelism, for CPU-bound workloads it is usually Environment.ProcessorCount. With this configuration the parallel execution will use all the cores of your machine, without too much thread-switching.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Thank you, but I fail to see how your answer improves over my problem. Let's say at one day you have some processes running in the background, by using processor count you put too much pressure on CPU for given conditions. Secondly with known in advance number of jobs, this is equivalent of `Parallel.For(0,Environment.ProcessorCount...` Thank you for comment on partitioning, I was not aware of this though I noticed the execution looks like random, now I know why :-). – greenoldman Dec 17 '22 at 05:08
  • 1
    @greenoldman I updated my answer with a simpler suggestion. This suggestion, as well as the original, will put the CPU under 100% pressure, which is presumably the desirable behavior. This will happen under the condition that the `Worker.RandomTest` method is exclusively CPU-Bound, and there is no other bottleneck (like RAM bandwidth) that will prevent the CPU from working continuously at its full potential. – Theodor Zoulias Dec 17 '22 at 13:46