Лучшие практики многопоточного проектирования

StackOverflow https://stackoverflow.com/questions/10229

  •  08-06-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top