Многопоточность нескольких потоков производителя и потребителя не будет синхронизировать условие гонки BlockingCollection

У меня есть несколько производителей и несколько потребителей. Мой общий ресурс — BlockingCollection. Однако мой код работает, только если у меня есть один потребитель. Я знаю, что это состояние гонки, так как вывод каждый раз разный, когда я запускаю код.

Я думал, что BlockingCollection позаботится обо всей синхронизации и т. д., но это не так.

Как тогда я могу синхронизировать свой общий ресурс между всеми производителями и потребителями?

Вот мой код:

/// <summary>
/// PURE PRODUCER TYPE
/// </summary>
class Caller
{

    private BlockingCollection<Call> incommingCalls;

    public Caller(BlockingCollection<Call> calls)
    {
        incommingCalls = calls;
        //start the producer thread
        Thread thread = new Thread(new ThreadStart(placeCall));
        thread.Start();
    }

    public void placeCall()
    {
            incommingCalls.Add(myCall);
    }

}



/// <summary>
/// CONSUMER
/// </summary>
class Fresher : Employee
{

    private BlockingCollection<Call> calls;

    public Fresher(BlockingCollection<Call> incalls)
    {
        calls = incalls;
        Thread thread = new Thread(new ThreadStart(HandleCalls));
        thread.Start();

    }

    /// <summary>
    /// 
    /// </summary>
    public void HandleCalls()
    {
        while (!incommingCalls.IsCompleted)
        {
            Call item;
            if (incommingCalls.TryTake(out item, 100000))
            {
                //do something with the call

            } //else do nothing - just wait

        }


}






/// <summary>
/// 
/// </summary>
class CallCenter
{

    private BlockingCollection<Call> fresherCalls;


    private List<Caller> myCallers;
    private List<Employee> myFreshers;


    public CallCenter() 
    {
        //initial incomming calls to the fresher queue
        fresherCalls = new BlockingCollection<Call>();

        myFreshers = new List<Employee>();
        myCallers = new List<Caller>();

        generate_freshers();

        //generate to start the producer
        generate_callers();

    }

    /// <summary>
    /// 
    /// </summary>
    private void generate_freshers() 
    {
        for (int i = 0; i < 1; i++ )
        {
            myFreshers.Add(new Fresher(fresherCalls, tlCalls, locker2));
        }
    }

    /// <summary>
    /// 
    /// </summary>
    private void generate_callers() 
    {
        for (int i = 0; i < 20; i++ )
        {
            myCallers.Add(new Caller(fresherCalls, locker));
        }

    }
}

person user1261710    schedule 28.01.2013    source источник
comment
Какое поведение, которое вы видите, которое вы ожидаете, является неправильным?   -  person Reed Copsey    schedule 28.01.2013
comment
Я ожидаю, что первый сотрудник примет первый звонок, второй примет второй звонок и т. д., но все они вращаются вокруг, и результат непредсказуем...   -  person user1261710    schedule 28.01.2013
comment
Вы не можете гарантировать, что вызовы обрабатываются в одном и том же порядке, если у вас несколько производителей/потребителей. Это будет зависеть от планировщика задач операционной системы. Что заставляет вас говорить, что что-то не работает? Он пропускает звонки или обрабатывает их неправильно, когда у вас несколько потребителей?   -  person Jim Mischel    schedule 28.01.2013
comment
Нет гарантии упорядочения с таким BlockingCollection<T>... В общем, конкретное упорядочение имеет тенденцию резко уменьшать или сводить на нет преимущества, которые вы получаете от параллельного программирования. Проблемы, которые хорошо масштабируются в параллельном пространстве, как правило, относятся к задачам, для которых порядок обработки не имеет значения.   -  person Reed Copsey    schedule 28.01.2013
comment
@user1261710 user1261710 Если вам нужно, чтобы результаты производились и потреблялись в определенном порядке, вы также не можете распараллелить.   -  person Servy    schedule 28.01.2013
comment
Все вызовы обрабатываются в какой-то момент моего кода. Я полагаю, если у вас есть только один потребитель, он всегда будет в порядке? Извините, я новичок в многопоточности в .NET.   -  person user1261710    schedule 28.01.2013
comment
В вашем примере с колл-центром ограничение, согласно которому звонки обрабатываются сотрудниками в порядке очереди сотрудников, было бы катастрофическим — один особенно длинный звонок (например, я жалуюсь моему интернет-провайдеру на их безнадежно-ненадежный DNS-сервер) свяжет одного сотрудника и В результате все остальные сотрудники сидят без дела, ожидая, пока мой занятый сотрудник ответит на вызовы, которые находятся на удержании. Типичная записанная реклама: Ваш звонок находится в очереди, и на него ответит ближайший доступный представитель.   -  person Martin James    schedule 28.01.2013


Ответы (1)


Я знаю, что это состояние гонки, так как вывод каждый раз разный, когда я запускаю код.

Это характерно для многопоточности и не обязательно из-за состояния гонки (по крайней мере, неплохого). Обработка заказов в многопоточных сценариях, как правило, не является детерминированной, что, вероятно, изменит результат.

При этом с BlockingCollection<T> обычно проще написать ваших потребителей как:

public void HandleCalls()
{
    foreach(var item in incommingCalls.GetConsumingEnumerable())
    {
        //do something with the call
    }
}

Это выполнит всю синхронизацию и проверку для вас для любого количества потребителей на BlockingCollection<T>.


Изменить: если вам нужно контролировать планирование и внедрить некоторую форму циклического планирования , вы можете ознакомиться с дополнениями к параллельным расширениям в образцах для TPL. Они предоставляют RoundRobinTaskScheduler, который можно использовать для планирования Task<T> экземпляров, работающих предсказуемым образом.

person Reed Copsey    schedule 28.01.2013
comment
Я пробовал так раньше, но это не изменило странный порядок задач.... :( - person user1261710; 28.01.2013
comment
@ user1261710 Порядок задач должен быть непредсказуемым при использовании нескольких производителей и нескольких потребителей. - person Reed Copsey; 28.01.2013
comment
Я понимаю, почему это было бы правдой. Однако, если очередь пуста и я добавляю ее, первый сотрудник должен ждать и действовать немедленно. Если это никогда не предсказуемо, то как я узнаю, что мой код работает правильно? - person user1261710; 28.01.2013
comment
@user1261710 user1261710 Нет - когда вы добавите один, будет ждать несколько сотрудников - один из них обработает его, но нет возможности узнать (или контролировать), какой сотрудник возьмет предмет. - person Reed Copsey; 28.01.2013
comment
@ user1261710 Только что отредактировал ответ, чтобы предоставить другую альтернативу. - person Reed Copsey; 28.01.2013
comment
Я считаю, что Round-Robin даст результаты, которые я хочу видеть :) Нужно ли мне полностью менять свой код? Спасибо! - person user1261710; 28.01.2013
comment
давайте продолжим это обсуждение в чате - person user1261710; 28.01.2013