4

I am trying to understand the benefits of Task Parallel library over using traditional multi threading and when I think about the below situation, I am stuck thinking does it handle race condition or do we need to handle this in our code?

Here is my code:

 int depdt = 0;    
 Parallel.For(1, 10, mem =>
        {

            for (int dep = 1; dep <= 10; dep++)
            {
                depdt = dep;
                Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
            }

            Console.WriteLine("Dep Value: " + depdt + " " + "mem: " + mem);
        });
        Console.ReadKey();

I ran it couple of times and I don't see any thread interfering/overwriting the the "depdt" variable. But I need to confirm this. (or) To make it thread safe should I manually create an instance of class and implement it like the below code to avoid race conditions

 int depdt = 0;
        Parallel.For(1, 10, mem =>
        {
              Worker worker = new Worker();
              worker.DoWork(mem);

        });
        Console.ReadKey();

  public class Worker
{
    public void DoWork(int mem)
    {

        int depdt = 0;
        for (int dep = 1; dep <= 10; dep++)
        {
            depdt = dep;
            Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
        }

        Console.WriteLine("Dep Value: " + depdt  +" "+ "mem: "+ mem);
    }
}

Response to @yms: I mean when using normal threads the varaible depdt becomes unreliable. Here is my code:

for (int mem = 1; mem <= 10; mem++)
        {
            var t= new Thread(state =>
            {
                for (int dep = 1; dep <= 10; dep++)
                {
                    depdt = dep;
                    Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
                }

                Console.WriteLine("Dep Value: " + depdt + " " + "mem: " + mem);
            });

            t.Start(string.Format("Thread{0}", mem));
        }
        Console.ReadKey();

Here is my output screen: Infact both mem and dep variables have become unreliable

enter image description here

Douglas
  • 53,759
  • 13
  • 140
  • 188
marak
  • 260
  • 3
  • 19
  • Note that variable assignments of 4-byte int [are usually atomics](http://stackoverflow.com/questions/8290768/is-assignment-operator-atomic). Also I do not see the need of introducing a new class, you could just have used a local variable in your lambda expression – yms Aug 20 '15 at 18:29
  • @yms I mean by using normal Threading you usually get into that state where "depdt" variable becomes unreliable. See my below code and its result with Threads – marak Aug 20 '15 at 18:40
  • @yms I added more info in the question answering your comment. Thanks – marak Aug 20 '15 at 18:49

2 Answers2

3

If you expect your program to always write Dep Value: 10, then yes, your program is subject to a race condition that may lead to other values being printed. To demonstrate the issue, just introduce a delay within the inner loop:

int depdt = 0;
Parallel.For(1, 10, mem =>
{
    for (int dep = 1; dep <= 10; dep++)
    {
        depdt = dep;
        Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
        Thread.Sleep(mem * 100);   // delay introduced here
    }
    Console.WriteLine("Dep Value: " + depdt + " " + "mem: " + mem);
});
Console.ReadKey();

The reason that your program appears to behave correctly is that the inner loop takes very little time to execute, probably completing within a single time quantum allocated to the thread.

To avoid the race condition, you just need to move the depdt declaration inside the anonymous function passed to Parallel.For. This would cause each thread to have its own variable, avoiding conflicts.

Parallel.For(1, 10, mem =>
{
    int depdt = 0;
    for (int dep = 1; dep <= 10; dep++)
    {
        depdt = dep;
        Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
    }
    Console.WriteLine("Dep Value: " + depdt + " " + "mem: " + mem);
});
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • Thank you Douglas. So, the gist is TPL does not have any inbuilt mechanism to handle race condition, it should be handled in our code. – marak Aug 20 '15 at 19:04
  • 1
    @marak: [Shared memory parallelism](https://en.wikipedia.org/wiki/Parallel_programming_model#Shared_memory) is inherently susceptible to race conditions. Since you declared your variable as shared across all threads, then it was *expected* for multiple threads to be able to access it concurrently. This is actually sometimes desired behaviour, as in the case of sentinel flags for terminating background operations. – Douglas Aug 20 '15 at 19:19
1

No. Task parallel library does not handles the race conditions by default. You need to take care of synchronizing the access to the shared resources.