In the previous articles, we have read part of Parallel Loop and Task. In multi-threaded programming, we have a pain point in data sharing and data synchronization in multiple threads. This Post and several subsequent Post articles will discuss common problems of data sharing in TPL and their common solutions.
Example
First, let's look at the following code:
Static void Main (string [] args)
{
Int Sum = 0;
Task [] tasks = new Task [10];
For (int I = 0; I <10; I ++)
{
Tasks [I] = new Task () =>
{
For (int j = 0; j <1000; j ++)
{
Sum = Sum + 1;
}
});
Tasks [I]. Start ();
}
Task. WaitAll (tasks );
Console. WriteLine ("Expected value {0}, Parallel value: {1 }",
10000, Sum );
Console. ReadLine ();
} The above Code is expected to get 10 threads to round-robin and accumulate the Sum data. According to our expectation, the calculated result should be: 10000, but let's look at the running result:
Why? As a matter of fact, we will understand that we perform the Sum operation in parallel in multiple threads, we can use a sequence chart to show the situation that the two threads simultaneously operate Sum (the statement in may be incorrect, please understand ):
The sum value obtained by Task 1 is 0, and Task 2 is also 0. After both tasks perform the 0 + 1 operation, the records are recorded in Sum, instead of accumulating the calculated information between our expected threads. In parallel programming, such data sharing problems are very common. In the above problem, we can solve this problem through sequential execution, but this is not what we want. After all, we still want to process it in parallel. A separate execution method is provided in TPL to solve this problem.
Separate execution
We can isolate the execution by passing the state parameter in the Task:
Static void Main (string [] args)
{
Int Sum = 0;
Task <int> [] tasks = new Task <int> [10];
For (int I = 0; I <10; I ++)
{
Tasks [I] = new Task <int> (obj) =>
{
Var start = (int) obj;
For (int j = 0; j <1000; j ++)
{
Start = start + 1;
}
Return start;
}, Sum );
Tasks [I]. Start ();
}
Task. WaitAll (tasks );
For (var I = 0; I <10; I ++)
{
Sum + = tasks [I]. Result;
}
Console. WriteLine ("Expected value {0}, Parallel value: {1 }",
10000, Sum );
Console. ReadLine ();
} In the above program, there is no data sharing during the execution of each Task. Each Task calculates its own value. Finally, we summarize the results of each Task. It seems that our problem has been solved, but we need to know that separation is not supported in. Net runtime. That is to say, every time we operate a Task, we must ensure that there is no data sharing between tasks, which is indeed troublesome. System. Threading. ThreadLocal is provided in. Net to create the separation.
ThreadLocal
ThreadLocal is a type that provides local thread storage. It can provide a separate instance for each thread to provide separate data results for each thread. In the above program, we can use TreadLocal:
Static void Main (string [] args)
{
Int Sum = 0;
Task <int> [] tasks = new Task <int> [10];
Var tl = new ThreadLocal <int> ();
For (int I = 0; I <10; I ++)
{
Tasks [I] = new Task <int> (obj) =>
{
Tl. Value = (int) obj;
For (int j = 0; j <1000; j ++)
{
Tl. Value ++;
}
Return tl. Value;
}, Sum );
Tasks [I]. Start ();
}
Task. WaitAll (tasks );
For (var I = 0; I <10; I ++)
{
Sum + = tasks [I]. Result;
}
Console. WriteLine ("Expected value {0}, Parallel value: {1 }",
10000, Sum );
Console. ReadLine ();
} Note that TreadLocal is for each thread rather than for each Task. The TreadLocal constructor provides an overloaded method to set the initial value of each thread Result. Let's take a look at the following example:
Static void Main (string [] args)
{
Int Sum = 0;
Task <int> [] tasks = new Task <int> [10];
Var tl = new ThreadLocal <int> () => {
Console. WriteLine (Sum );
Return Sum;
});
For (int I = 0; I <10; I ++)
{
Tasks [I] = new Task <int> () =>
{
For (int j = 0; j <1000; j ++)
{
Tl. Value ++;
}
Return tl. Value;
});
Tasks [I]. Start ();
}
Task. WaitAll (tasks );
For (var I = 0; I <10; I ++)
{
Sum + = tasks [I]. Result;
}