Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/visual-studio-2010/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在.NET中写入写入列表上的副本_C#_.net_Multithreading_Lock Free_Copy On Write - Fatal编程技术网

C# 如何在.NET中写入写入列表上的副本

C# 如何在.NET中写入写入列表上的副本,c#,.net,multithreading,lock-free,copy-on-write,C#,.net,Multithreading,Lock Free,Copy On Write,如何在.NET中使用写时复制模型编写线程安全列表 下面是我当前的实现,但在阅读了大量关于线程、内存障碍等的内容后,我知道在涉及到无锁的多线程时,我需要谨慎。有人能评论一下这是否是正确的实现方式吗 class CopyOnWriteList { private List<string> list = new List<string>(); private object listLock = new object(); public void Ad

如何在.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];
    }
}
类CopyOnWriteList
{
私有列表=新列表();
私有对象listLock=新对象();
公共无效添加(字符串项)
{
锁(列表锁)
{
列表=新列表(列表){item};
}
}
公共无效删除(字符串项)
{
锁(列表锁)
{
var tmpList=新列表(列表);
tmpList.移除(项目);
列表=tmpList;
}
}
公共布尔包含(字符串项)
{
返回列表。包含(项目);
}
公共字符串Get(int索引)
{
返回列表[索引];
}
}
编辑

更具体地说:以上代码是线程安全的,还是应该添加更多内容?另外,所有线程最终会在
列表中看到更改吗?或者我应该在列表字段或线程中添加
volatile
关键字。MemoryBarrier在访问引用和调用引用方法之间包含方法

举个例子,看起来像我上面的代码,但这种方法在.NET中也是线程安全的吗

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


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

实现是正确的,因为引用分配是原子的,符合。我想将
volatile
添加到
列表中
您的方法看起来是正确的,但我建议使用
字符串[]
而不是
列表
来保存数据。添加项时,您确切地知道结果集合中将包含多少项,因此可以创建一个新数组,其大小与所需的大小完全相同。删除项目时,您可以获取
列表
参考的副本,并在制作副本之前搜索您的项目;如果发现该项不存在,则无需删除它。如果确实存在,则可以创建一个新数组,该数组的大小与所需的大小完全相同,并将要删除的项之前或之后的所有项复制到新数组中

另一件你可能要考虑的事情是使用<代码> int [1 ] < /Cord>作为你的锁定标志,并使用一个类似于:

的模式。
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;
}

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

是的,它是线程安全的:

  • 添加
    删除
    中的集合修改是在单独的集合上完成的,因此它避免了从
    添加
    删除
    或从
    添加
    /
    删除
    包含
    /
    获取
    同时访问同一集合

  • 新集合的分配是在
    lock
    内部完成的,这只是一对监视器。输入和
    Monitor.Exit
    ,这两个监视器都会像前面提到的那样设置一个完整的内存屏障,这意味着锁定后所有线程都应该观察
    列表
    字段的新值


  • 正确性在于使用,无需重新发明轮子。考虑一下这个问题:@Cuong Le:根据MSDN,ConcurrentBag“针对同一线程同时产生和消耗数据的场景进行了优化”。我需要对罕见的写操作和频繁的读操作进行优化,一次写一次就可以做到这一点。@Alvin Wong:这不是我需要的。1) 我不想使用F#libs 2)immutable collection只是“写时拷贝”的一部分,谢谢你的回答,但我想实现的是从列表中无锁读取。我目前使用ReaderWriterLockSlim,但希望学习如何在没有它的情况下使用“写时复制”模式。谢谢,现在的问题是:是否需要“volatile”使所有线程在“list”引用中看到更改?请查看MSDN中的
    volatile
    。它说,声明为volatile的
    字段不受编译器优化的影响,编译器优化假定由单个线程访问。这可确保字段中始终存在最新的值。
    即读和写保证按其显示顺序进行,读\写保证为原子。你需要它吗是的,我看了“写时复制”,因为在我的用例中,从列表中添加和删除是很少的(比如一个月一次),但检查项目是否存在是非常频繁的(一秒钟多次)。我不关心在Add和Remove中锁定的优化,我需要Contains方法的性能。“写时复制”保证了这一点,但我不确定我上面的实现是否是.NET内存模型中的线程保存。@RafałKłys:您的代码处于一个非常令人讨厌的灰色区域,因为大多数.NET实现都是在x86内存模型上运行的,而x86内存模型在实现方面比.NET内存模型更严格(用户方面更宽容)。为了完全安全,可能需要在
    tmpList.Remove
    list=tmpList
    之间添加一个显式内存屏障(我认为
    System.Threading.Thread.MemoryBarrier()
    );我很确定在这种情况下是不需要的