我有一个包含1,000个对象的列表,每个对象都包含自己的LockObject。
我计划启动运行同样的事情的多个线程。他们将从1,000个列表中挑选6个随机对象,然后锁定它们的锁定对象。在释放它们之前对它们做一些事情。
当我创建6个对象的列表时,我想使用该循环,假设我按每个对象中的一些唯一值对列表进行排序,以下内容会避免2个或更多这样的线程之间的死锁情况吗?
Players = Players.OrderBy(P => P.ID).ToList();
Lock(Players[0].LockObj)
Lock(Players[1].LockObj)
Lock(Players[2].LockObj)
Lock(Players[3].LockObj)
Lock(Players[4].LockObj)
Lock(Players[5].LockObj)
{
//...
}
我正在阅读这篇文章,试图弄清楚这是否是写这篇文章的“正确”方式,看起来是这样,但是人们给出了关于死锁的警告。我相信这会避免死锁,但不确定100%。
假设你按升序锁定,那么没有死锁是可能的。你可以通过归纳法证明这一点。
不打算展示完整的教授,但这是我的想法。
给定一个n个锁的列表,如果您以升序锁定它们,那么任何在lockn
上持有锁的线程都将始终能够完成,因为它是最后一项,并且在等待之后不会有任何内容。
因为锁n总是可以完成,所以锁n-1也可以,因为锁n是唯一可以等待的锁n-1。
所以通过感应锁定0-n将始终能够完成并且没有死锁是可能的。
由于您首先对列表进行排序,因此不应该死锁,除非另一个线程尝试从不同的代码中无序锁定相同的对象。
在实际需要对象之前锁定它们通常是个坏主意。如果您正在执行需要一次访问所有六个对象才能执行的计算,那么很难绕过这一点,但是如果您只是对六个对象中的每一个执行操作,则应该一次锁定一个。
for(int i = 0; i < 6; ++i)
{
lock(Players[i].LockObj)
{
//Do Stuff
}
}
如果您从多个线程使用此代码,您当前的代码可能会显着降级。如果不同的线程试图锁定同一个对象,您最终可能会遇到除了一个线程之外的每个线程都停止等待轮到它们运行的情况,从而导致缓慢的同步行为。
如果您正在执行需要访问所有六个对象的计算,最好一次锁定一个对象,复制您需要的信息,然后解锁它们。然后进行计算,一次再次锁定一个对象,复制您的更改,然后解锁它们。
如果你的计算要求玩家不要从你复制数据时的状态改变,你可以在编辑时使用跟踪变量或哈希来检测数据何时被编辑,然后你可以选择处理这种情况的策略:撤销已经对其他玩家所做的更改并重新计算?接受你正在覆盖来自另一个线程的更改?做一些其他操作?