Освобождает ли вызов WaitOne с помощью семафора вызывающий поток для выполнения другой работы? - PullRequest
0 голосов
/ 30 октября 2018

Мое приложение должно выполнять несколько задач для каждого арендатора каждую минуту. Это операции запуска и забывания, поэтому я не хочу использовать Parallel.ForEach для этого.

Вместо этого я перебираю список арендаторов и запускаю ThreadPool.QueueUserWorkItem для обработки каждой задачи арендаторов.

foreach (Tenant tenant in tenants)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessTenant), tenantAccount);
}

Этот код отлично работает на производстве и может обрабатывать более 100 клиентов менее чем за 5 секунд.

Однако при запуске приложения это приводит к 100% загрузке ЦП, в то время как такие вещи, как EF, нагреваются в процессе запуска. Чтобы ограничить это, я реализовал семафор следующим образом:

private static Semaphore _threadLimiter = new Semaphore(4, 4);

Идея состоит в том, чтобы ограничить обработку этой задачи только возможностью использовать только половину машинных логических процессоров. Внутри метода ProcessTenant я вызываю:

try
{
    _threadLimiter.WaitOne();

    // Perform all minute-to-minute tasks
}
finally
{
    _threadLimiter.Release();
}

При тестировании это работает точно так, как ожидалось. Загрузка ЦП при запуске остается на уровне около 50% и, по-видимому, не влияет на скорость первоначального запуска.

Так что вопрос главным образом в том, что происходит на самом деле, когда вызывается WaitOne. Выпускает ли это поток для работы над другими задачами - аналогично асинхронным вызовам? В документации MSDN говорится, что WaitOne: «Блокирует текущий поток, пока текущий WaitHandle не получит сигнал.»

Так что я просто опасаюсь, что это не позволит моему веб-приложению продолжать использовать этот заблокированный поток во время его ожидания, что сделает весь смысл этого упражнения бессмысленным.

1 Ответ

0 голосов
/ 30 октября 2018

WaitOne блокирует поток, и этот поток перестанет планироваться на ядре ЦП, пока семафор не будет сигнализирован. Тем не менее, вы удерживаете большое количество потоков из пула потоков, возможно, в течение длительного времени («long», например, «long ~ ~ 500 ms»). Это может быть проблемой, потому что пул потоков растет очень медленно, поэтому вы можете помешать другой части вашего приложения правильно использовать его.

Если вы планируете ждать значительное количество времени, вы можете использовать вместо этого свои собственные потоки:

foreach (Tenant tenant in tenants)
{
    new Thread(ProcessTenant).Start(tenantAccount);    
}

Тем не менее, вы все еще сохраняете один поток на элемент в памяти. Хотя они не будут использовать процессор, так как они спят на семафоре, они все еще используют ОЗУ даром (около 1 МБ на поток). Вместо этого сделайте так, чтобы один выделенный поток ожидал семафор и поставил новые элементы в очередь при необходимости:

// Run this on a dedicated thread
foreach (Tenant tenant in tenants)
{
    _threadLimiter.WaitOne();
    ThreadPool.QueueUserWorkItem(_ => 
    {
        try         
        {
            ProcessTenant(tenantAccount);
        }
        finally
        {
            _threadLimiter.Release();
        }
    });
}
...