【问题标题】:The C# lock construction misunderstanding (with msdn code example)C#锁构造误区(附msdn代码示例)
【发布时间】:2018-07-05 04:51:56
【问题描述】:

我对使用 C# lock 构造的一件事感兴趣 现在来自 MSDN 的示例,然后是主要问题:

以下示例使用线程和锁。只要 lock 语句存在,语句块就是临界区,balance 永远不会变成负数。

class Account
{
    private Object thisLock = new Object();
    int balance;

    Random r = new Random();

    public Account(int initial)
    {
        balance = initial;
    }

    int Withdraw(int amount)
    {

        // This condition never is true unless the lock statement
        // is commented out.
        if (balance < 0)
        {
            throw new Exception("Negative Balance");
        }

        // Comment out the next line to see the effect of leaving out 
        // the lock keyword.
        lock (thisLock)
        {
            if (balance >= amount)
            {
                Console.WriteLine("Balance before Withdrawal :  " + balance);
                Console.WriteLine("Amount to Withdraw        : -" + amount);
                balance = balance - amount;
                Console.WriteLine("Balance after Withdrawal  :  " + balance);
                return amount;
            }
            else
            {
                return 0; // transaction rejected
            }
        }
    }

    public void DoTransactions()
    {
        for (int i = 0; i < 100; i++)
        {
            Withdraw(r.Next(1, 100));
        }
    }
}

class Test
{
    static void Main()
    {
        Thread[] threads = new Thread[10];
        Account acc = new Account(1000);
        for (int i = 0; i < 10; i++)
        {
            Thread t = new Thread(new ThreadStart(acc.DoTransactions));
            threads[i] = t;
        }
        for (int i = 0; i < 10; i++)
        {
            threads[i].Start();
        }

        //block main thread until all other threads have ran to completion.
        foreach (var t in threads)
            t.Join();
    }
}

我不明白为什么使用lock 帐户余额不会减负;它总是以0 余额结束编译。 P.S 对不起我的英语。

【问题讨论】:

  • 请记住Random 不是线程安全的,因此您也不能在lock 之外使用它。
  • 值得注意的是,如果您将for (int i = 0; i &lt; 100; i++) 更改为for (int i = 0; i &lt; 2; i++),您的程序将以正余额结束。绝大多数for 循环在计数到 100 时是空操作,因为余额已经为零。

标签: c# multithreading


【解决方案1】:

锁在这个例子中按预期工作,在启动多个线程时,它们都会尝试从帐户中退出,这可能会导致非常糟糕的功能。怎么样?
好吧,假设当前余额为 40,线程 1 尝试提取 40,线程 2 尝试提取 20,如果没有锁,它们都会成功,并且余额将是 -20,这是不能接受的。

现在让您真正关心的是,为什么余额没有变为负数?简单:

lock (thisLock)
    {
        if (balance >= amount)
        {
            Console.WriteLine("Balance before Withdrawal :  " + balance);
            Console.WriteLine("Amount to Withdraw        : -" + amount);
            balance = balance - amount;
            Console.WriteLine("Balance after Withdrawal  :  " + balance);
            return amount;
        }
        else
        {
            return 0; // transaction rejected
        }
    }

锁将确保每个线程只有在余额可用时才会退出,因此条件if (balance &gt;= amount)加上if (balance &lt; 0)将确保余额不会变为负数。

如果您记录每个线程提取的金额,您可以看到详细信息:

Console.WriteLine(Withdraw(r.Next(1, 100)));

您会看到其中很多会在一段时间后输出 0,因为帐户不再有余额,因此触发了 return 0

【讨论】:

  • 一切都清楚了,我问了一个愚蠢的问题。现在我仔细检查了代码,发现我没有注意到if (balance &gt;= amount)
【解决方案2】:

您正在生成十个线程,它们几乎同时启动并同时运行。 Console.WriteLine 调用与递减操作相比是耗时的,因此没有lock 多个线程可以并且将进入由if (balance &gt;= amount) 保护的语句块(因为到那时可能仍然有足够的余额)任何其他线程已经即将到达并执行balance = balance - amount 语句。

(没有Console.WriteLine 调用,问题是一样的 - 竞争的可能性可能会更低,但仍然需要lock,除非关键部分是原子操作)。

使用lock,不会有两个线程同时进入临界区,因此如果进入临界区时余额已经为零,它将安全地分支到else 块上面写着“交易被拒绝”。

我认为,一旦您意识到这是锁定语法的真正目的 - 防止需要序列化读取和/或写入访问的资源出现竞争条件(这里,资源是平衡变量),您的误解就会得到解决)。从那里开始,下一个挑战是volatile 关键字documented here

【讨论】:

    【解决方案3】:

    我不是 100% 确定的,但我假设 MSDN 提供的示例以类似生产者 - 消费者的模式显示了这一点。如果没有lock,多个线程处理同一数据(即balance 变量)可能会意外出错。 lock 确保在任何给定时间只有一个线程可以读取和写入 balance

    【讨论】:

    • 但是为什么它从来没有进入减号,它每次都以0帐户结束编译?关于在任何给定时间读/写毫无疑问,很清楚
    • @Sparkll 看看锁里的条件
    • 这会让您的回答变得不必要?除非你编辑它^^
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-04
    • 1970-01-01
    • 2010-10-23
    • 2017-04-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多