并行编程竞争条件

时间:2011-12-05 07:55:16

标签: c# multithreading

我正在研究种族条件的并行编程示例

在示例中,他们展示了处理竞争条件的隔离模式

为什么在此示例中,在创建任务时未发生竞争条件且stateObject作为任务创建的一部分传递

据我所知,我们使用了IsolatedBalance来进行更新......但是在我们分配isolatedbalance = (int)stateObject时,其他任务不能完成平衡,即不是0而是100 ???

因此,如果有足够的任务,并且任务调度程序启动了一个早期任务,并且它在创建后一个任务的时刻结束并且已经结算了帐户。当1个任务已经完成时,平衡值将为100等对于正在开始的那个人来说

class BankAccount
{
    public int Balance { get; set; } 
}

class Program
{
    static void Main(string[] args)
    {

        var account = new BankAccount();
        var tasks = new Task<int>[1000];


        for (int i = 0; i < 1000; i++)
        {
            tasks[i] = new Task<int>((stateObject)=>
            {
                int isobalance = (int) stateObject;
                for (int j = 0; j < 1000; j++)
                {
                    isobalance ++;
                }
                return isobalance;
             }, account.Balance);

             tasks[i].Start();
        }
        Task.WaitAll(tasks);

        for (int i = 0; i < 1000; i++)
        {
            account.Balance += tasks[i].Result;
        }

        Console.WriteLine("Epectecd valeu {0}, Counter value {1}",1000000,account.Balance);

        // wait for input before exiting
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

2 个答案:

答案 0 :(得分:4)

您传递给Task构造函数的方法未更新account.Balance它仅使用account.Balance的初始值。它没有更新它。 int是按值传递的。来自MSDN

值类型变量直接包含其数据,而不是包含对其数据的引用的引用类型变量。因此,将值类型变量传递给方法意味着将变量的副本传递给方法。对方法内发生的参数的任何更改都不会影响存储在变量中的原始数据。如果希望被调用的方法更改参数的值,则必须使用ref或out关键字通过引用传递它。为简单起见,以下示例使用ref。

因此,account.Balance直到调用Task.WaitAll(tasks);后才会更新。 Task.WaitAll()导致代码停止,直到所有任务完成。只有在那之后,一旦计算出所有结果。将使用account.Balance返回的值更新tasks[i].Result

答案 1 :(得分:1)

它不会导致竞争条件,因为您只复制account.Balance的当前值并将其分配给线程内的局部变量。创建每个线程后,他们只需将account.Balance的当前值复制到其堆栈上,然后复制到本地变量,但没有线程实际更改它,它们都在本地副本上工作。想象一下这就像一个方法调用。将int传递给方法时,它会按值复制,然后即使您在方法中修改它,也不会在外部看到任何更改。

话虽如此,我最喜欢的例子来说明你要问的是非常常见的“为每个线程分配一个唯一的id”问题。考虑这两种情况:

不是线程安全的:

for(int i = 0; i < n; i++) 
{
    Thread t = new Thread(
       o =>
       {
           int index = i;
           // do whatever
       });
    t.Start();
}

这不是线程安全的,因为当线程在代码中使用它时,主线程继续遍历i。当每个线程实际开始时,我可能已经达到了n。

主题安全的:

for(int i = 0; i < n; i++) 
{
    Thread t = new Thread(
       o =>
       {
           int index = (int)o;
           // do whatever
       });
    t.Start(i);
}

根据我的初步解释,这是线程安全的。每个线程在创建时接收i的当前值并将其复制到局部变量内,这样线程将正确地具有id 0,1,...,n-1。我希望这个例子能说得更清楚。