需要改进的线程锁定建议

时间:2015-05-28 13:53:38

标签: c# multithreading locking

我有以下情况:

我正在尝试锁定一个线程,如果该线程的“自定义”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)。

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

只需一个普通的锁即可修复,但在这种情况下,我仍然想要一个当前没有在CreateTestObject(_id)方法内运行_id的线程,以便能够在锁内输入代码。

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

2 个答案:

答案 0 :(得分:3)

看起来您正在使用此代码以线程安全的方式填充字典 - 您是否可以使用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旋转直到设置对象,这可以被重写为阻塞。

答案 1 :(得分:0)

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

您可以使用Semaphore代替Monitor来解决此问题,但请停一会儿并听取您的要求 - 您希望线程能够阻止同一个线程拥有的lock。该线程永远如何唤醒?它将永远陷入僵局 - 等待lock被释放,同时也是持有lock的人。

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

ConcurrentDictionary<int, YourObject> dictionary;

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

请注意,如果词典中不存在该键,则CreateTextObject仅称为