交换参考变量值的最快且仍然安全的方法

时间:2012-10-16 17:04:51

标签: c# multithreading thread-safety locking

基本上我需要的是能够不断地向List(或其他集合)添加项目,在一个线程中每秒大约3000次。并且每2秒钟从该列表中获取和删除所有项目。

我不喜欢经典的方法,比如使用并发集合或每次我需要访问集合时锁定某些内容,因为它会比我需要的更慢。

我要做的是拥有2个集合,每个线程一个,并找到一种方法使线程安全地从一个集合切换到另一个集合。

简化而非线程安全示例:

var listA = new List<int>();
var listB = new List<int>();

// method is called externally 3000 times per second
void ProducerThread(int a)
{      
    listA.Add(a)      
}
void ConsumerThread()
{
  while(true)
  {
    Thread.Sleep(2000);
    listB = Interlocked.Exchange(ref listA,listB);
    //... processing listB data
    // at this point when i'm done reading data
    // producer stil may add an item because ListA.Add is not atomic
    // correct me if i'm wrong
    listB.Clear();
  }
}

有没有办法让上面的代码按预期工作(线程安全),同时尽可能少地阻止生产者线程?或者可能是另一种解决方案?

3 个答案:

答案 0 :(得分:2)

我首先在System.Collections.Concurrent中使用BlockingCollection或其他IProducerConsomerCollection开始。这正是您所拥有的,从多个线程访问的生产者/消费者队列。这些系列还针对性能进行了大量优化。他们不使用天真的“任何人做任何操作时锁定整个结构”。它们足够聪明,可以使用无锁同步技术尽可能地避免锁定,并且当它们确实需要使用关键部分时,它们可以最小化需要锁定的内容,以便尽管存在一定量的锁定,通常可以同时访问该结构。

在我从那里移动到其他任何东西之前,我会使用其中一个集合并确保它太慢。如果在使用它作为您的解决方案后,您已经证明您花费了不可接受的时间来添加/删除集合中的项目,那么您可以考虑调查其他解决方案。

如果我怀疑是这样的话,他们的表现足够快,我相信你会发现这使得编写代码更多更容易阅读。

答案 1 :(得分:1)

我假设您只想处理listA的新添加内容,并且在处理这些添加内容时会进行更多添加。

var listA = new List<int>();
var dictA = new Dictionary<int,int>();

int rangeStart = 0;
int rangeEnd = 0;
bool protectRange = false;

// method is called externally 3000 times per second
void ProducerThread(int a)
{      
 listA.Add(a);
 dictA.Add(rangeEnd++,a);   
}
void ConsumerThread()
{
 while(true)
 {
  Thread.Sleep(2000);
  int rangeInstance = rangeEnd;
  var listB = new List<int>();
  for( int start = rangeStart; start < rangeInstance; start++ ){
   listB.add(dictA[start]);
   rangeStart++;
  }
  //... processing listB data
  }
}

答案 2 :(得分:0)

如果表格有最大固定大小,为什么要使用List?您也可以预先设置列表大小。

List<int> listA = new List<int>(6000);

现在,我还没有真正测试过以下内容,但我认为它会做你想做的事情:

int[] listA = new int[6000]; // 3000 time * 2 seconds
int i = 0;

// method is called externally 3000 times per second
void ProducerThread(int a)
{
    if (Monitor.TryEnter(listA)) // If true, consumer is in cooldown.
    {
        listA[i] = a;
        i++;
        Monitor.Exit(listA);
    }
}

void ConsumerThread()
{
    Monitor.Enter(listA); // Acquire thread lock.

    while (true)
    {
        Monitor.Wait(listA, 2000); // Release thread lock for 2000ms, automaticly retake it after Producer released it.

        foreach (int a in listA) { } //Processing...

        listA = new int[6000];
        i = 0;
    }
}

你只需要确保首先运行ConsumerThread,这样它就会排队并等待。