问题是我一直在使用lock 语句来保护我的代码的关键部分,但现在,我意识到我可以允许该关键代码的并发执行,前提是满足某些条件。
有没有办法调节锁?
8 回答
我认为这个问题叫“种族条件!”。如果在检查后不久条件从真变为假,但在线程进入代码的关键部分之前怎么办?或者当一个线程正在执行它的过程中?
bool locked = false;
if (condition) {
Monitor.Enter(lockObject);
locked = true;
}
try {
// possibly critical section
}
finally {
if (locked) Monitor.Exit(lockObject);
}
编辑:是的,除非您可以确保在线程进入时条件是恒定的,否则存在竞争条件。
我不是线程专家,但听起来您可能正在寻找类似的东西(双重检查锁定)。这个想法是在获取锁之前和之后检查条件。
private static object lockHolder = new object();
if (ActionIsValid()) {
lock(lockHolder) {
if (ActionIsValid()) {
DoSomething();
}
}
}
Action doThatThing = someMethod;
if (condition)
{
lock(thatThing)
{
doThatThing();
}
}
else
{
doThatThing();
}
实际上,为了避免竞争条件,我很想在ReaderWriterLockSlim
这里使用 - 将并发访问视为读锁,将独占访问视为写锁。这样,如果条件发生变化,您将不会在该区域中仍然盲目执行一些不适当的代码(在错误假设它是安全的情况下);有点冗长,但是(格式化为空格):
if (someCondition) {
lockObj.EnterReadLock();
try { Foo(); }
finally { lockObj.ExitReadLock(); }
} else {
lockObj.EnterWriteLock();
try { Foo(); }
finally { lockObj.ExitWriteLock(); }
}
如果您有许多需要条件锁定的方法/属性,您不想一遍又一遍地重复相同的模式。我提出以下技巧:
非重复条件锁模式
使用私有助手struct
实现IDisposable
,我们可以封装条件/锁而没有可测量的开销。
public void DoStuff()
{
using (ConditionalLock())
{
// Thread-safe code
}
}
这很容易实现。这是一个演示此模式的示例类:
public class Counter
{
private static readonly int MAX_COUNT = 100;
private readonly bool synchronized;
private int count;
private readonly object lockObject = new object();
private int lockCount;
public Counter(bool synchronized)
{
this.synchronized = synchronized;
}
public int Count
{
get
{
using (ConditionalLock())
{
return count;
}
}
}
public int LockCount
{
get
{
using (ConditionalLock())
{
return lockCount;
}
}
}
public void Increase()
{
using (ConditionalLock())
{
if (count < MAX_COUNT)
{
Thread.Sleep(10);
++count;
}
}
}
private LockHelper ConditionalLock() => new LockHelper(this);
// This is where the magic happens!
private readonly struct LockHelper : IDisposable
{
private readonly Counter counter;
private readonly bool lockTaken;
public LockHelper(Counter counter)
{
this.counter = counter;
lockTaken = false;
if (counter.synchronized)
{
Monitor.Enter(counter.lockObject, ref lockTaken);
counter.lockCount++;
}
}
private void Exit()
{
if (lockTaken)
{
Monitor.Exit(counter.lockObject);
}
}
void IDisposable.Dispose() => Exit();
}
}
现在,让我们创建一个小示例程序来证明其正确性。
class Program
{
static void Main(string[] args)
{
var onlyOnThisThread = new Counter(synchronized: false);
IncreaseToMax(c1);
var onManyThreads = new Counter(synchronized: true);
var t1 = Task.Factory.StartNew(() => IncreaseToMax(c2));
var t2 = Task.Factory.StartNew(() => IncreaseToMax(c2));
var t3 = Task.Factory.StartNew(() => IncreaseToMax(c2));
Task.WaitAll(t1, t2, t3);
Console.WriteLine($"Counter(false) => Count = {c1.Count}, LockCount = {c1.LockCount}");
Console.WriteLine($"Counter(true) => Count = {c2.Count}, LockCount = {c2.LockCount}");
}
private static void IncreaseToMax(Counter counter)
{
for (int i = 0; i < 1000; i++)
{
counter.Increase();
}
}
}
输出:
Counter(false) => Count = 100, LockCount = 0
Counter(true) => Count = 100, LockCount = 3002
现在您可以让调用者决定是否需要锁定(代价高昂)。
我猜你有一些看起来有点像这样的代码:
private Monkey GetScaryMonkey(int numberOfHeads){
Monkey ape = null;
lock(this) {
ape = new Monkey();
ape.AddHeads(numberOfHeads);
}
return ape;
}
要使这个有条件,你不能这样做:
private Monkey GetScaryMonkey(int numberOfHeads){
if ( numberOfHeads > 1 ) {
lock(this) {
return CreateNewMonkey( numberOfHeads );
}
}
return CreateNewMonkey( numberOfHeads );
}
应该工作,不是吗?
如上所述,使用双重检查锁定模式。这就是 IMO 的诀窍 :)
确保您将锁定对象设置为static,如 not.that.dave.foley.myopenid.com 的示例中所列。