如何解决Lock和Mutex显示出不同的结果
我正在尝试一些与C#线程中的锁和互斥相关的概念。但是,如果发现使用Mutex可以给我正确的结果,而使用锁则不能令人满意。
使用lock
构造:
class BankAccount
{
private int balance;
public object padlock = new object();
public int Balance { get => balance; private set => balance = value; }
public void Deposit(int amount)
{
lock ( padlock )
{
balance += amount;
}
}
public void Withdraw(int amount)
{
lock ( padlock )
{
balance -= amount;
}
}
public void Transfer(BankAccount where,int amount)
{
lock ( padlock )
{
balance = balance - amount;
where.Balance = where.Balance + amount;
}
}
}
static void Main(string[] args)
{
var ba1 = new BankAccount();
var ba2 = new BankAccount();
var task = Task.Factory.StartNew(() =>
{
for ( int j = 0; j < 1000; ++j )
ba1.Deposit(100);
});
var task1 = Task.Factory.StartNew(() =>
{
for ( int j = 0; j < 1000; ++j )
ba2.Deposit(100);
});
var task2 = Task.Factory.StartNew(() =>
{
for ( int j = 0; j < 1000; ++j )
ba1.Transfer(ba2,100);
});
Task.WaitAll(task,task1,task2);
Console.WriteLine($"Final balance is {ba1.Balance}.");
Console.WriteLine($"Final balance is {ba2.Balance}.");
Console.ReadLine();
}
在ba2
设置为0的情况下,代码为ba1
提供了不正确的余额。
即使每个操作都被lock
语句包围,情况仍然如此。它无法正常工作。
使用Mutex
构造:
class BankAccount
{
private int balance;
public int Balance { get => balance; private set => balance = value; }
public void Deposit(int amount)
{
balance += amount;
}
public void Withdraw(int amount)
{
balance -= amount;
}
public void Transfer(BankAccount where,int amount)
{
balance = balance - amount;
where.Balance = where.Balance + amount;
}
}
static void Main(string[] args)
{
var ba1 = new BankAccount();
var ba2 = new BankAccount();
var mutex1 = new Mutex();
var mutex2 = new Mutex();
var task = Task.Factory.StartNew(() =>
{
for ( int j = 0; j < 1000; ++j )
{
var lockTaken = mutex1.WaitOne();
try
{
ba1.Deposit(100);
}
finally
{
if ( lockTaken )
{
mutex1.ReleaseMutex();
}
}
}
});
var task1 = Task.Factory.StartNew(() =>
{
for ( int j = 0; j < 1000; ++j )
{
var lockTaken = mutex2.WaitOne();
try
{
ba2.Deposit(100);
}
finally
{
if ( lockTaken )
{
mutex2.ReleaseMutex();
}
}
}
});
var task2 = Task.Factory.StartNew(() =>
{
for ( int j = 0; j < 1000; ++j )
{
bool haveLock = Mutex.WaitAll(new[] { mutex1,mutex2 });
try
{
ba1.Transfer(ba2,100);
}
finally
{
if ( haveLock )
{
mutex1.ReleaseMutex();
mutex2.ReleaseMutex();
}
}
}
});
Task.WaitAll(task,task2);
Console.WriteLine($"Final balance is {ba1.Balance}.");
Console.WriteLine($"Final balance is {ba2.Balance}.");
Console.ReadLine();
}
通过这种方法,每次运行时我都会获得正确的余额。
我无法弄清楚为什么第一种方法无法正常工作。我是否在锁定语句方面缺少某些东西?
解决方法
主要问题在于此行:
public int Balance { get => balance; private set => balance = value; }
您允许外部代码插入balance
字段,而没有padlock
的保护。您还允许对balance
字段进行无序读取,因为缺少memory barrier,或者更糟糕的是torn reads,以防以后替换int
类型与更合适的decimal
。
第二个问题可以通过使用padlock
保护读取来解决。
public int Balance { get => { lock (padlock) return balance; } }
对于Transfer
方法,现在可以实现它而无需访问其他BankAccount
个balance
,如下所示:
public void Transfer(BankAccount where,int amount)
{
Withdraw(amount);
where.Deposit(amount);
}
这个Transfer
实现不是原子的,因为where.Deposit
方法中的异常可能导致amount
消失van尽。同样,不会阻止其他线程读取两个BankAccount
的不一致值。这就是为什么人们通常使用具有ACID属性的数据库来进行此类工作。
这两个代码在我的计算机 VS2017 .NET Framework 4.7.2 上给出相同的结果,并且工作正常。因此,可能与您的系统有所不同。
Final balance is 0.
Final balance is 200000.
Mutex从历史上讲,最初是用于进程间同步的。
因此,在创建互斥锁的过程中,除非像问题中提供的代码那样释放它,否则它永远不会锁定自身。
使用操作系统互斥对象同步线程是一种不好的做法,也是一种反模式。
如果lock
和volatile
遇到问题,请在流程中使用Semaphore或Monitor。
Mutex:“也可以用于进程间同步的同步原语。”
信号量:“限制可以同时访问资源或资源池的线程数。”
监视器:“提供一种同步对对象的访问的机制。”
lock:“ lock语句获取给定对象的互斥锁,执行语句块,然后释放该锁。在持有锁的同时,持有该锁的线程可以再次获取并释放锁。其他任何线程都被阻止获取锁,并等待直到锁释放。”
volatile:“ volatile关键字指示一个字段可能被同时执行的多个线程修改。编译器,运行时系统甚至硬件可能会重新安排对内存位置的读写,以提高性能原因:被声明为volatile的字段不受这些优化的影响;添加volatile修饰符可确保所有线程都将按照执行顺序对其他任何线程执行的volatile写入进行观察。从执行的所有线程来看都是易失性的。”
因此,您可以尝试添加volatile
:
private volatile int balance;
如果需要,您还可以将锁对象设置为静态对象,以便在实例之间共享:
static private object padlock = new object();
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。