【问题标题】:Improved Thread locking advice needed需要改进的线程锁定建议
【发布时间】:2015-05-28 13:53:37
【问题描述】:

我有以下场景:

我正在尝试锁定一个线程,如果该线程的“自定义”ID 与已经进入锁定部分代码的那个匹配,但如果 ID 不同则不是。

我创建了一些示例代码来解释我想要的行为

class A
{        
    private static Dictionary<int, object> _idLocks = new Dictionary<int, object>();
    private static readonly object _DictionaryLock = new object();
    private int _id;

    private void A (int id)
    {
        _id = id;
    }

    private object getObject()
    {
        lock (_DictionaryLock)
        {
            if (!_idLocks.ContainsKey(_id))
                _idLocks.Add(_id, new object());
        }
        lock (_idLocks[_id])
        {
            if (TestObject.Exists(_id))
                return TestObject(_id);
            else
                return CreateTestObject(_id);
        }
    }
}

现在这对于我扩展的内容 100% 有效,其中 id 示例 1 不会检查其对象是否已创建,而另一个 id 为 1 的线程已经忙于创建该对象。

但是拥有两个锁和一个静态字典似乎根本不是正确的方法,所以我希望有人可以向我展示一种改进的方法,仅当该线程是用相同的创建线程时才停止访问代码id 已经忙于执行锁定部分中的代码。

我正在查看 ReaderWriterLockSlim 类,但对我来说使用它并没有什么意义,因为我不希望在对象 TestObject(id) 仍在创建时被读取。

我不关心锁定线程访问字典。 我要不惜一切代价避免在CreateTestObject(_id)中使用该线程运行的_id,而已经有一个忙,因为正在使用该id创建和删除文件,如果两个线程都将引发异常试图访问相同的文件

这可以通过普通锁来修复,但在这种情况下,我仍然希望其 _id 当前未在 CreateTestObject(_id) 方法中运行的线程能够在锁中输入代码。

这都是因为 CreateTestObject 内部发生的事情需要时间,如果线程正在等待访问它,性能会受到影响。

【问题讨论】:

  • 是否可以对整个 _idLocks 字典进行锁定而不是使用 2 个锁定?我对锁很陌生,所以我不确定你可以将什么用作锁对象。

标签: c# multithreading locking


【解决方案1】:

您似乎正在使用此代码以线程安全的方式填充字典 - 您可以改用 ConcurrentDictionary 吗?

class A {
  private static ConcurrentDictionary<int, object> _dictionary = new ConcurrentDictionary<int, object>();

  private int _id;

  private object GetObject() {
    object output = null;
    if(_dictionary.TryGetValue(_id, output)) {
      return output;
    } else {
      return _dictionary.GetOrAdd(_id, CreateTestObject(_id));
    }
  }
}

编辑:如果您想完全消除调用重复 CreateTestObject 方法的可能性,那么您可以在 _dictionary 中存储一个包装器,该包装器会延迟设置 object

class Wrapper {
  private volatile object _obj = null;

  public object GetObj() {
    while(_obj == null) {
      // spin, or sleep, or whatever
    }
    return _obj;
  }

  public void SetObj(object obj) {
    _obj = obj;
  } 
}

class A {
  private static ConcurrentDictionary<int, Wrapper> _dictionary = new ConcurrentDictionary<int, Wrapper>();

  private int _id;

  private object GetObject() {
    Wrapper wrapper = null;
    if(_dictionary.TryGetValue(_id, wrapper)) {
      return wrapper.GetObj();
    } else {
      Wrapper newWrapper = new Wrapper();
      wrapper = _dictionary.GetOrAdd(_id, newWrapper);
      if(wrapper == newWrapper) {
        wrapper.SetObj(CreateTestObject(_id));
      }
      return wrapper.GetObj();
    }
  }
}

只有一个线程能够将新的Wrapper 放入指定的_dictionary 中的_id - 该线程将在wrapper == newWrapper 条件下初始化对象。 Wrapper#GetObj 旋转直到对象被设置,这可以被重写为块。

【讨论】:

  • 我不知道有ConcurrentDictionary。 +1!
  • 嗨,我更新了我原来的问题,使其更加清晰,感谢您迄今为止的帮助
  • 谢谢,这是一种不同的方法,但我相信我可以让它为我工作谢谢
【解决方案2】:

这不起作用,因为Monitor(由lock 语句在内部使用)是可重入的。这意味着一个线程可以多次进入它已经拥有的任何锁。

您可以通过使用Semaphore 而不是Monitor 来解决此问题,但请停下来听听您的要求 - 您希望线程阻塞同一线程拥有的lock .该线程永远将如何唤醒?它将永远死锁 - 等待lock 被释放,同时也是持有lock 的人。

或者你只是想处理一些对象的延迟初始化而不必阻塞所有其他线程?这其实很简单:

ConcurrentDictionary<int, YourObject> dictionary;

return dictionary.GetOrAdd(id, i => CreateTestObject(i));

请注意,CreateTextObject 仅在字典中不存在该键时才被调用

【讨论】:

  • 嗨,我更新了我原来的问题,使其更加清晰,感谢您迄今为止的帮助
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多