我做了一个如何安全地从多个线程访问资源的例子。
线程不安全版本
class Program
{
private static decimal _balance = 100;
static void Main(string[] args)
{
for (var i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(Withdraw, 23m);
}
Console.ReadLine();
}
private static void Withdraw(object state)
{
var value = (decimal)state;
var prevBalance = _balance;
if (_balance >= value)
{
Thread.Sleep(50); // simulate longer operation to emphasize data corruption
_balance = _balance - value;
Console.WriteLine("Balance {0}. Withdraw {1}. Balance after withdraw {2}", prevBalance, value, _balance);
}
Console.WriteLine("Balance {0}. Withdraw failed", prevBalance);
}
}
此代码将导致意外(和无法预测)的结果,因为变量_balance 是同时从多个线程访问和写入的。 _balance 可以为负数,即使我们在提款前检查余额。
结果
线程安全版本
为了使线程以上的withdraw方法安全,你需要将方法体包裹在lock语句中。
class Program
{
private static decimal _balance = 100;
private static readonly object _balanceLocker = new object();
static void Main(string[] args)
{
for (var i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(Withdraw, 23m);
}
Console.ReadLine();
}
private static void Withdraw(object state)
{
lock (_balanceLocker)
{
var value = (decimal)state;
var prevBalance = _balance;
if (_balance >= value)
{
Thread.Sleep(50); // simulate longer operation to emphasize data corruption
_balance = _balance - value;
Console.WriteLine("Balance {0}. Withdraw {1}. Balance after withdraw {2}", prevBalance, value, _balance);
return;
}
Console.WriteLine("Balance {0}. Withdraw {1}. Withdraw failed", prevBalance, value);
}
}
}
使用上面的代码,即使_withdraw 被多个线程访问,它也永远不会为零。
结果