Рекомендации по многопоточному проектированию

Рассмотрим эту проблему: у меня есть программа, которая должна получить (скажем) 100 записей из базы данных, а затем для каждой из них она должна получить обновленную информацию из веб-службы. Есть два способа ввести параллелизм в этом сценарии:

  1. Я запускаю каждый запрос к веб-службе в новом потоке. Количество одновременных потоков контролируется каким-то внешним параметром (или каким-то образом динамически настраивается).

  2. Я создаю меньшие партии (скажем, по 10 записей в каждой) и запускаю каждую партию в отдельном потоке (в нашем примере 10 потоков).

Какой подход лучше и почему вы так думаете?


person Vaibhav    schedule 13.08.2008    source источник
comment
Динамический / настраиваемый, поскольку оптимальное число зависит от среды и того, что на самом деле является узким местом.   -  person Stu    schedule 13.08.2008
comment
Это похоже на работу для ThreadPool. Просто поставьте задания в очередь, и пусть .net сделает все остальное.   -  person Patrick    schedule 13.08.2008
comment
@Patrick Патрик Ну, я думал о ThreadPool с точки зрения динамического управления. Но я думаю, что пытаюсь выяснить, есть ли какая-либо разница в производительности между двумя подходами (на самом деле ThreadPool можно использовать в обоих). И если не производительность, то есть ли какая-то передовая практика, которой следует следовать.   -  person Vaibhav    schedule 13.08.2008


Ответы (4)


Вариант 3 самый лучший:

Используйте асинхронный ввод-вывод.

Если обработка вашего запроса не является сложной и тяжелой, ваша программа будет тратить 99% своего времени на ожидание HTTP-запросов.

Это именно то, для чего предназначен асинхронный ввод-вывод. Пусть сетевой стек Windows (или .net framework или что-то еще) беспокоится обо всем ожидании и просто использует один поток для отправки и «получения» результатов.

К сожалению, .NET framework делает это занозой в заднице. Это проще, если вы используете необработанные сокеты или Win32 API. Вот (протестированный!) пример с использованием С# 3 в любом случае:

using System.Net; // need this somewhere

// need to declare an class so we can cast our state object back out
class RequestState {
    public WebRequest Request { get; set; }
}

static void Main( string[] args ) {
    // stupid cast neccessary to create the request
    HttpWebRequest request = WebRequest.Create( "http://www.stackoverflow.com" ) as HttpWebRequest;

    request.BeginGetResponse(
        /* callback to be invoked when finished */
        (asyncResult) => { 
            // fetch the request object out of the AsyncState
            var state = (RequestState)asyncResult.AsyncState; 
            var webResponse = state.Request.EndGetResponse( asyncResult ) as HttpWebResponse;

            // there we go;
            Debug.Assert( webResponse.StatusCode == HttpStatusCode.OK ); 

            Console.WriteLine( "Got Response from server:" + webResponse.Server );
        },
        /* pass the request through to our callback */
        new RequestState { Request = request }  
    );

    // blah
    Console.WriteLine( "Waiting for response. Press a key to quit" );
    Console.ReadKey();
}

РЕДАКТИРОВАТЬ:

В случае .NET «обратный вызов завершения» фактически запускается в потоке ThreadPool, а не в вашем основном потоке, поэтому вам все равно нужно будет блокировать любые общие ресурсы, но это по-прежнему избавляет вас от всех проблем с управлением потоками.

person Orion Edwards    schedule 13.08.2008
comment
Вам действительно нужно передать запрос, используя объект состояния, или вы можете использовать запрос как связанную переменную закрытия? - person zvikara; 23.12.2008

Две вещи, которые следует учитывать.

1. Сколько времени займет обработка записи?

Если обработка записей выполняется очень быстро, накладные расходы на передачу записей потокам могут стать узким местом. В этом случае вы захотите объединить записи, чтобы вам не приходилось передавать их так часто.

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

2. Сколько потоков вы планируете запустить?

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

person Derek Park    schedule 13.08.2008
comment
Да, это полезные соображения. Поскольку это вызывает общедоступный веб-сервис, я думаю, мы могли бы запустить некоторые тесты, чтобы увидеть, больше ли накладных расходов, чем сама работа (я сомневаюсь в этом). И да, использование ThreadPool — это то, что мы определенно рассмотрели бы. - person Vaibhav; 13.08.2008

Компьютер, на котором запущена программа, вероятно, не является узким местом, поэтому: Помните, что протокол HTTP имеет заголовок проверки активности, который позволяет вам отправлять несколько запросов GET на одни и те же сокеты, что избавляет вас от рукопожатия TCP/IP. К сожалению, я не знаю, как использовать это в библиотеках .net. (Должно быть возможно.)

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

person Hugo    schedule 13.08.2008

Получите Parallel Fx. Посмотрите на BlockingCollection. Используйте поток, чтобы передать ему пакеты записей, и потоки от 1 до n, извлекающие записи из коллекции для обслуживания. Вы можете контролировать скорость загрузки коллекции и количество потоков, которые вызывают веб-службы. Сделайте его настраиваемым с помощью ConfigSection и сделайте его универсальным, передав коллекцию делегатов Action, и у вас будет хороший маленький дозатор, который вы можете повторно использовать в свое удовольствие.

person Community    schedule 13.08.2008