并行编程竞争条件

本文关键字:条件 竞争 编程 并行 | 更新日期: 2023-09-27 18:16:43

我正在研究一个关于竞态条件的并行编程示例

在这个例子中,他们展示了隔离模式来处理竞争条件

为什么在下面这个例子中,创建任务时没有出现竞争条件,而stateObject是作为任务创建的一部分传递的?

我知道我们使用isolatedBalance来做更新…但在我们分配isolatedbalance = (int)stateObject的时候,其他任务完成的平衡不是0而是100 ??

因此,如果有足够的任务,并且任务调度程序启动了一个较早的任务,并且它在创建较晚的任务并分配帐户时完成。当其中一个任务完成时,余额值将为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();
    }
}

并行编程竞争条件

您传递给Task构造函数的方法不更新account.Balance,它只使用account.Balance的初始值。它不会更新它。int是按值传递的。从MSDN:

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

因此account.Balance直到Task.WaitAll(tasks);被调用后才更新。Task.WaitAll()使代码停止在那里,直到所有任务完成。只有在那之后,当所有的结果都计算出来之后。account.Balance是否会用tasks[i].Result返回的值进行更新?

它不会引起竞争条件,因为您只复制account的当前值。平衡并将其分配给线程内的一个局部变量。在创建每个线程时,它们只是复制account的当前值。在它们的堆栈上进行平衡,然后到一个局部变量,但是没有线程实际改变它,它们都在本地副本上工作。把它想象成一个方法调用。当你传递一个int给一个方法时,它被按值复制,然后即使你在方法内部修改它,你也不会看到任何变化。

说到这里,我最喜欢的例子来说明你所问的是非常常见的"为每个线程分配一个唯一的id"问题。考虑以下两种情况:

不是线程安全的:

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

这不是线程安全的,因为主线程在线程在其代码中使用它时继续循环i。当每个线程t实际启动时,它可能已经达到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。