Лучшая практика многопоточного дизайна

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

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

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

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

13.08.2008 19:03:21
Динамический / настраиваемый, так как оптимальное количество зависит от окружающей среды и узкого места.
Stu 13.08.2008 19:07:43
Это звучит как работа для ThreadPool . Просто поставьте в очередь задания и позвольте .net справиться с остальными.
Patrick 13.08.2008 19:09:01
@Patrick Ну, я думал о ThreadPool с точки зрения динамического управления. Но я думаю, что я пытаюсь выяснить, есть ли какая-либо разница в производительности между двумя подходами (на самом деле ThreadPool можно использовать в обоих). И если не производительность, есть ли лучшая практика, которой следует следовать.
Vaibhav 13.08.2008 19:10:35
4 ОТВЕТА
РЕШЕНИЕ

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

Используйте Async IO.

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

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

К сожалению, .NET Framework делает правильную боль в заднице. Это проще, если вы просто используете сырые сокеты или Win32 API. Вот (проверено!) Пример использования C # 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, а не в вашем основном потоке, поэтому вам все равно придется блокировать любые общие ресурсы, но это все же избавляет вас от проблем управления потоками.

6
10.01.2014 16:30:51
Вам действительно нужно передать запрос, используя объект состояния, или вы можете использовать этот запрос как переменную, связанную с замыканием?
zvikara 22.12.2008 21:32:49

Две вещи для рассмотрения.

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

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

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

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

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

2
13.08.2008 19:19:32
Да, это полезные соображения. Так как это вызывает общедоступный веб-сервис, поэтому я думаю, что мы могли бы захотеть запустить несколько тестов, чтобы увидеть, являются ли издержки больше, чем сама работа (я сомневаюсь в этом). И да, использование ThreadPool - это то, что мы определенно рассмотрели бы.
Vaibhav 13.08.2008 19:22:50

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

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

0
13.08.2008 19:34:24

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

0
13.08.2008 19:40:41