modules ...">

How do multiple threads access a list when each list item needs to be blocked?

I have a list of classes "the Module", List<Module> modules

. Each of these modules contains its own public object for use as a data access lock. Let's say I have a couple of threads that are doing random processing on these modules. Currently, each thread is processing modules in order, for example:

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

      

This has worked well so far, but I feel like a lot of unnecessary waiting. For example, if two threads start one after the other, but the first is doing heavy processing and the second is not, the second must wait for each module while the first is doing its processing.

Here's the question: is there a "correct" or "most efficient" way to lock items in a list? I was going to do this:

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

      

Where "Randomize ()" is just an extension method that returns the elements of the list in random order. However, I was wondering if there is an even better way than random?

+3


source to share


3 answers


Assuming the work inside the castle is huge and has a tough competition. I was introducing additional overhead of creating new List<T>

and removing items from them.

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--;
        }
    }
}

      



What this method does is copy the passed modules, then tries to acquire the lock, if it cannot be acquired (because another thread owns it) it skips and moves. After the end of the list, it reappears to see if another thread has released the lock, unless it skips again and moves. This cycle continues until we have processed all the modules in the list.

This way we do not expect any approved locks, we just continue to process modules that are not blocked by another thread.

+1


source


lock

means Monitor.Enter

, you can use Monitor.TryEnter

to check if the lock is not locked and somehow skip this element and try to take another one.

If multiple threads are processing the same ordered list of items, there will be an overhead, so the idea with Randomize

seems like a good one (unless the reordering is expensive compared to the processing itself, or the list might be changed during processing, etc.).

A completely different possibility is to prepare queues (from the list) for each thread in such a way that there is no cross-wait (or the wait will be minimized). Combined with Monitor.TryEnter

this, this should be the final decision. Unfortunately, I don't know how to prepare such queues and how to skip the processing queue item, leaving that for you = P.




Here is a snippet of what I mean:

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

      

+1


source


Not sure if I'm completely following, however from what I can tell, your goals are to do stuff periodically for each module and you want to use multiple threads because stuff takes a long time. If so, I will have a single thread periodically check all modules and use that thread for TPL to propagate the workload, e.g .:

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

    }
});

      

As an aside, the guide to locking is that the object you are locking must be private, so I will probably move on to doing something like this:

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
    }
}

      

those. each module must be thread safe and responsible for its own locking.

+1


source







All Articles