当每个列表元素必须被锁定时,多个线程应如何访问列表?

时间:2015-01-06 08:59:00

标签: c# list concurrency locking

我有一个&#34;模块&#34;课程,List<Module> modules。这些模块每个都包含自己的公共对象,以便在访问数据时用作锁。让我们说我有几个线程在随机时间对这些模块执行处理。目前我让每个线程按顺序对模块执行处理,如下所示:

foreach (Module module in modules)
{
    lock (module.Locker)
    {
        //Do stuff
    }
}

到目前为止,这种方法运作良好,但我感觉有很多不必要的等待。例如,如果两个线程一个接一个地开始,但第一个线程正在执行繁重的处理,而第二个线程不是,则第二个线程必须等待每个模块,而第一个模块正在进行处理。

这是一个问题:是否有适当的&#34;或者&#34;效率最高的&#34;锁定列表中元素的方法?我打算这样做:

foreach (Module module in modules.Randomize())
{
    lock (module.Locker)
    {
        //Do stuff
    }
}

其中&#34;随机化()&#34;只是一种以随机顺序返回列表元素的扩展方法。但是,我想知道是否有比随机更好的方式?

3 个答案:

答案 0 :(得分:1)

lock代表Monitor.Enter,您可以使用Monitor.TryEnter检查是否已获取锁定,并以某种方式跳过此元素并尝试接受另一个元素。

如果多个线程正在处理相同的有序的项目列表,则会有开销,因此使用Randomize的想法似乎很好(除非重新排序与处理本身相比是昂贵的,或者列表可以在处理等时更改。)

完全另一种可能性是为每个线程准备队列(从列表中),这样就不会有交叉等待(或等待最小化)。结合Monitor.TryEnter这应该是一个终极解决方案。不幸的是,我不知道如何准备这样的队列,也不知道如何跳过处理队列项,留给你= P.


以下是我的意思摘要:

foreach(var item in list)
    if(!item.Processed && Monitor.TryEnter(item.Locker))
        try
        {
            ... // do job
            item.Processed = true;
        }
        finally
        {
            Monitor.Exit(item.Locker))
        }

答案 1 :(得分:1)

假设锁内的工作量很大且争用很大。我正在引入额外的开销,即创建新的List<T>并从中删除项目。

public void ProcessModules(List<Module> modules)
{
    List<Module> myModules = new List<Module>(modules);//Take a copy of the list
    int index = myModules.Count - 1;
    while (myModules.Count > 0)
    {
        if (index < 0)
        {
            index = myModules.Count - 1;
        }

        Module module = myModules[index];
        if (!Monitor.TryEnter(module.Locker))
        {
            index--;
            continue;
        }

        try
        {
            //Do processing module
        }
        finally
        {
            Monitor.Exit(module.Locker);
            myModules.RemoveAt(index);
            index--;
        }
    }
}

这个方法的作用是获取传入的模块的副本,然后尝试获取锁,如果不可能获取它(因为另一个线程拥有它),它会跳过并继续。完成列表后,再次看到另一个线程是否释放了锁,如果没有再次跳过它并继续前进。这个循环一直持续到我们处理列表中的所有模块为止。

这样,我们不会等待任何争用锁,我们只是继续处理未被另一个线程锁定的模块。

答案 2 :(得分:1)

不确定我是否完全遵循,但是我可以告诉您的目标是定期对每个模块执行操作,并且您希望使用多个线程,因为这些内容非常耗时。如果是这种情况,我会有一个单线程定期检查所有模块并让该线程使用TPL来分散工作负载,如下所示:

Parallel.ForEach(modules, module =>
{
    lock(module.Locker)
    {

    }
});

顺便说一下,关于锁的指导是你锁定的对象应该是私有的,所以我可能会改为做这样的事情:

Parallel.ForEach(modules, module => module.DoStuff());

// In the module implementation
private readonly object _lock = new object();

public void DoStuff()
{
    lock (this._lock)
    {
        // Do stuff here
    }
}

即。每个模块都应该是线程安全的,并且负责自己的锁定。