2

如何在 .NET 中使用写时复制模型编写线程安全列表?

下面是我目前的实现,但是在阅读了大量关于线程、内存屏障等的内容之后,我知道在涉及无锁的多线程时我需要小心。如果这是正确的实现,有人可以评论吗?

class CopyOnWriteList
{
    private List<string> list = new List<string>();

    private object listLock = new object();

    public void Add(string item)
    {
        lock (listLock)
        {
            list = new List<string>(list) { item };
        }
    }

    public void Remove(string item)
    {
        lock (listLock)
        {
            var tmpList = new List<string>(list);
            tmpList.Remove(item);
            list = tmpList;
        }
    }

    public bool Contains(string item)
    {
        return list.Contains(item);
    }

    public string Get(int index)
    {
        return list[index];
    }
}

编辑

更具体地说:上面的代码是线程安全的,还是我应该添加更多内容?此外,所有线程最终都会看到list参考的变化吗?或者,也许我应该volatile在列表字段或 Thread.MemoryBarrier 中添加关键字访问引用和调用它的方法之间的包含方法?

这是例如Java实现,看起来像我上面的代码,但是这种方法在.NET中也是线程安全的吗?

这是同样的问题,但在 Java 中也是如此

是与此相关的另一个问题。

4

3 回答 3

0

实现是正确的,因为根据变量引用的原子性,引用分配是原子的。我会添加volatilelist.

于 2013-06-28T15:26:57.637 回答
0

您的方法看起来是正确的,但我建议使用 astring[]而不是 aList<string>来保存您的数据。当您添加一个项目时,您确切地知道结果集合中有多少项目,因此您可以创建一个完全符合所需大小的新数组。删除项目时,您可以获取list参考的副本并在复制之前搜索您的项目;如果事实证明该项目不存在,则无需将其删除。如果确实存在,您可以创建一个确切所需大小的新数组,并将要删除的项之前或之后的所有项复制到新数组中。

您可能要考虑的另一件事是使用 aint[1]作为您的锁定标志,并使用类似以下的模式:

static string[] withAddedItem(string[] oldList, string dat)
{
  string[] result = new string[oldList.Length+1];      
  Array.Copy(oldList, result, oldList.Length);
  return result;
}
int Add(string dat) // Returns index of newly-added item
{
  string[] oldList, newList;
  if (listLock[0] == 0)
  {
    oldList  = list;
    newList = withAddedItem(oldList, dat);
    if (System.Threading.Interlocked.CompareExchange(list, newList, oldList) == oldList)
      return newList.Length;
  }
  System.Threading.Interlocked.Increment(listLock[0]);
  lock (listLock)
  {
    do
    {
      oldList  = list;
      newList = withAddedItem(oldList, dat);
    } while (System.Threading.Interlocked.CompareExchange(list, newList, oldList) != oldList);
  }
  System.Threading.Interlocked.Decrement(listLock[0]);
  return newList.Length;
}

如果没有写争用,则CompareExchange无需获取锁即可成功。如果存在写争用,写操作将被锁序列化。请注意,这里的锁对于确保正确性既不是必要的,也不是充分的。其目的是避免在发生写入争用时出现抖动。线程#1 可能会通过其第一个“if”测试,并在许多其他线程同时尝试写入列表并开始使用锁时将任务任务切换出去。如果发生这种情况,线程#1 可能会通过执行自己的CompareExchange. 这样的操作会导致lock-holding 线程不得不浪费时间来创建一个新数组,但是这种情况应该很少出现,以至于额外数组副本的偶尔成本应该无关紧要。

于 2013-10-04T18:03:27.130 回答
0

是的,它是线程安全的:

  1. Add和中的集合修改是在单独的集合上完成的,因此它避免了从和或从/和/Remove对同一集合的并发访问。AddRemoveAddRemoveContainsGet

  2. 新集合的分配是在内部完成的lock,这只是一对 Monitor.Enter 和Monitor.Exit,它们都做了一个完整的内存屏障,如此处所述这意味着在锁定之后所有线程都应该观察list字段的新值。

于 2016-02-23T10:16:06.237 回答