C#lock构造的误解(以msdn代码为例)

C#lock构造的误解(以msdn代码为例),c#,multithreading,C#,Multithreading,我对使用C#锁构造感兴趣 现在是MSDN中的样本,然后是以下主要问题: 以下示例使用线程和锁。只要lock语句存在,语句块就是一个关键部分,余额永远不会变成负数 class Account { private Object thisLock = new Object(); int balance; Random r = new Random(); public Account(int initial) { balance = initi

我对使用C#
构造感兴趣 现在是MSDN中的样本,然后是以下主要问题:

以下示例使用线程和锁。只要lock语句存在,语句块就是一个关键部分,余额永远不会变成负数

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();
    }
}
类帐户
{
私有对象thisLock=新对象();
国际收支平衡;
随机r=新随机();
公共帐户(初始整数)
{
余额=初始值;
}
整数取款(整数金额)
{
//除非lock语句
//被注释掉了。
如果(余额<0)
{
抛出新异常(“负余额”);
}
//注释掉下一行以查看省略的效果
//锁定关键字。
锁(这个锁)
{
如果(余额>=金额)
{
控制台写线(“取款前余额:+余额”);
Console.WriteLine(“提取金额:-”+金额);
余额=余额-金额;
控制台写线(“提款后余额:+余额”);
退货金额;
}
其他的
{
返回0;//事务被拒绝
}
}
}
公共无效交易()
{
对于(int i=0;i<100;i++)
{
撤回(r.Next(1100));
}
}
}
课堂测试
{
静态void Main()
{
线程[]线程=新线程[10];
账户acc=新账户(1000);
对于(int i=0;i<10;i++)
{
螺纹t=新螺纹(新螺纹起点(acc.DoTransactions));
线程[i]=t;
}
对于(int i=0;i<10;i++)
{
线程[i].Start();
}
//阻塞主线程,直到所有其他线程运行完成。
foreach(线程中的变量t)
t、 Join();
}
}
我不明白为什么使用
lock
账户余额不会变成负数;它总是以
0
余额结束编译。
对不起,我的英语不好。

我不是100%肯定,但我假设MSDN提供的示例显示了类似生产者-消费者的模式。如果没有

lock
,多个线程处理同一数据(即
balance
变量)可能会无意中出错
lock
确保在任何给定时间只有一个线程可以读写balance。

您正在生成十个线程,这些线程几乎同时启动并同时运行。与递减操作相比,
控制台.WriteLine
调用非常耗时,因此如果没有
多个线程可以并且将进入由
if(balance>=amount)
保护的语句块(因为到那时可能仍然有足够的余额)其他任何线程都即将到达并执行
balance=balance-amount
语句

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

锁定
到位的情况下,不会有两个线程同时进入关键部分,因此如果进入关键部分时余额已经为零,它将安全地分支到
else
块,其中显示“事务已拒绝”


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

锁按照本例中的预期工作,在启动多个线程时,它们都会尝试从帐户中退出,这可能会导致非常糟糕的功能。怎么做?
好吧,假设当前余额是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>=amount)
if(balance<0)
相结合将确保余额不会变为负值

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

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

您将看到,很多帐户在一段时间后将输出0,因为帐户不再有余额,因此
返回0
会触发。

但为什么它从不进入负数,每次都以
0
帐户结束编译?关于任何给定时间的读/写毫无疑问clear@Sparkll看看锁中的条件,这使得你的答案不必要?除非你编辑它^^一切都很清楚,我问了一个愚蠢的问题。现在我仔细检查了代码,意识到我没有注意到
如果(余额>=amount)
请记住
随机
不是线程安全的,所以你也不能在
锁之外使用它。值得注意的是,如果你为(int i=0;i<100;i++)
to
for(int i=0;i<2;i++)
您的程序以正平衡结束。绝大多数
for
循环在计数到100时都是无操作的,因为平衡已经为零。