在MultiThreaded方案中正确锁定List <t>?</t>

时间:2009-09-01 15:09:37

标签: c# .net multithreading

好的,我无法正确理解多线程场景。很抱歉再次提出类似的问题,我只是在互联网上看到许多不同的“事实”。

public static class MyClass {
    private static List<string> _myList = new List<string>;
    private static bool _record;

    public static void StartRecording()
    {
        _myList.Clear();
        _record = true;
    }

    public static IEnumerable<string> StopRecording()
    {
        _record = false;
        // Return a Read-Only copy of the list data
        var result = new List<string>(_myList).AsReadOnly();
        _myList.Clear();
        return result;
    }

    public static void DoSomething()
    {
        if(_record) _myList.Add("Test");
        // More, but unrelated actions
    }
}

这个想法是,如果激活录制,对DoSomething()的调用将记录在内部List中,并在调用StopRecording()时返回。

我的说明是这样的:

  • StartRecording不被视为线程安全。用户应该在没有其他Thread调用DoSomething()时调用它。但如果它能以某种方式存在,那就太好了。
  • StopRecording也不是正式线程安全的。同样,如果它可能会很好,但这不是一个要求。
  • DoSomething必须是线程安全的

通常的方式似乎是:

    public static void DoSomething()
    {
        object _lock = new object();
        lock(_lock){
            if(_record) _myList.Add("Test");
        }
        // More, but unrelated actions
    }

或者,声明一个静态变量:

    private static object _lock;

    public static void DoSomething()
    {
        lock(_lock){
            if(_record) _myList.Add("Test");
        }
        // More, but unrelated actions
    }

但是,this answer表示这并不妨碍其他代码访问它。

所以我想知道

  • 如何正确锁定列表?
  • 我应该在我的函数中创建锁对象还是作为静态类变量?
  • 我是否可以在锁定块中包含Start和StopRecording的功能?
  • StopRecording()做两件事:将布尔变量设置为false(以防止DoSomething()添加更多东西),然后复制列表以将数据的副本返回给调用者。我假设_record = false;是原子的,会立即生效吗?所以通常我根本不用担心多线程,除非其他一些Thread再次调用StartRecording()?

在一天结束时,我正在寻找一种方式来表达“好吧,这个列表现在是我的,所有其他线程都必须等到我完成它”。

5 个答案:

答案 0 :(得分:31)

我会在这里锁定_myList本身,因为它是私有的,但使用单独的变量更常见。要改进几点:

public static class MyClass 
{
    private static List<string> _myList = new List<string>;
    private static bool _record; 

    public static void StartRecording()
    {
        lock(_myList)   // lock on the list
        {
           _myList.Clear();
           _record = true;
        }
    }

    public static IEnumerable<string> StopRecording()
    {
        lock(_myList)
        {
          _record = false;
          // Return a Read-Only copy of the list data
          var result = new List<string>(_myList).AsReadOnly();
          _myList.Clear();
          return result;
        }
    }

    public static void DoSomething()
    {
        lock(_myList)
        {
          if(_record) _myList.Add("Test");
        }
        // More, but unrelated actions
    }
}

请注意,此代码使用lock(_myList)来同步对_myList _record的访问。而且你需要同步这两个上的所有动作。

并且同意这里的其他答案,lock(_myList)对_myList没有任何作用,它只使用_myList作为标记(可能是HashSet中的键)。通过使用相同的令牌询问权限,所有方法都必须公平。另一个线程上的方法仍然可以使用_myList而不首先锁定,但结果不可预测。

我们可以使用任何令牌,因此我们经常专门创建一个令牌:

private static object _listLock = new object();

然后在任何地方使用lock(_listLock)代替lock(_myList)

如果myList是公开的,那么这种技术是可取的,如果你重新创建了myList而不是调用Clear(),那将是绝对必要的。

答案 1 :(得分:15)

DoSomething()中创建新锁定肯定是错误的 - 这将毫无意义,因为每次调用DoSomething()都会使用不同的锁定。您应该使用第二种形式,但使用初始化程序:

private static object _lock = new object();

确实,锁定不会阻止任何其他内容访问您的列表,但除非您直接公开列表,否则无关紧要:无论如何都不会访问该列表。

是的,你可以用同样的方式将Start / StopRecording包装在锁中。

是的,设置布尔变量是原子的,但这不会使其成为线程安全的。如果你只访问同一个锁中的变量,那么就原子性的波动性而言你都没问题。否则你可能会看到“陈旧”的价值 - 例如在一个线程中将值设置为true,另一个线程在读取时可以使用缓存值。

答案 2 :(得分:7)

有几种方法可以锁定列表。您可以直接锁定_myList 提供_myList永远不会更改为引用新列表。

lock (_myList)
{
    // do something with the list...
}

您可以专门为此目的创建一个锁定对象。

private static object _syncLock = new object();
lock (_syncLock)
{
    // do something with the list...
}

如果静态集合实现System.Collections.ICollection接口(List(T)),您还可以使用SyncRoot属性进行同步。

lock (((ICollection)_myList).SyncRoot)
{
    // do something with the list...
}

要理解的要点是,您希望 一个 只有一个 对象用作锁定sentinal,这就是为什么在DoSomething()函数内创建锁定sentinal将无法正常工作。正如Jon所说,每个调用DoSomething()的线程都会获得自己的对象,因此该对象的锁定每次都会成功并立即授予对该列表的访问权限。通过使锁定对象成为静态(通过列表本身,专用锁定对象或ICollection.SyncRoot属性),它将在所有线程之间共享,并且可以有效地序列化对列表的访问。

答案 3 :(得分:3)

第一种方式是错误,因为每个调用者都会锁定另一个对象。 你可以锁定列表。

lock(_myList)
{
   _myList.Add(...)
}

答案 4 :(得分:1)

你可能会误解this answer,实际上说的是lock语句实际上没有锁定有问题的对象被修改,而是阻止使用该对象的任何其他代码作为执行的锁定源。

这实际上意味着当您使用与锁定对象相同的实例时,锁定块内的代码不应该被执行。

从本质上讲,你并没有真正试图“锁定”你的列表,你试图有一个公共实例,当你想要修改你的列表时,你可以使用它作为参考点,当它被使用或“锁定”时“你想阻止其他代码执行,这可能会修改列表。