Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекции / Лекция 11

.docx
Скачиваний:
83
Добавлен:
17.06.2016
Размер:
243.24 Кб
Скачать

Лекция 11

  1. Общие вопросы управления потоками в распределнных приложениях 2. Пулы потоков 3. Настройка размера пула. Алгоритм адаптации размера пула

1.Общие вопросы управления потоками в распределнных приложениях

В примере из предыдущей лекции, в реализованном сервере за каждым вновь созданным сокетом закреплялся отдельный поток. Данный случай является наиболее простым случаем распределенной параллельной обработки и носит название «Поток на задачу». Иногда довольно разумно генерировать новый поток для каждой новой задачи. Однако, если частота создания задач высока, а их средняя продолжительность низка, порождение нового потока для каждой задачи приведет к проблемам с производительностью.

Возможны, однако и другие ситуации:

- с одним сокетом связано несколько потоков. Например, когда пользовательский запрос представляет собой сложную вычислительную задачу, которая подразумевает использование параллелизма, например, решение системы дифференциальных уравнений. Данный подход довольно неплохо работает, например, в реализации конвейерной обработки, когда число операций, выполняемых над клиентским запросом известно и за каждой операцией закрепляется отдельный поток. Например, конвейерная обработка сообщений. Подобные схемы распространены при построение шин интеграции распределенных приложений. Поступившее через сокет сообщение последовательно обрабатывается несколькими обработчиками, за каждым из которых закреплен отдельный поток:

-обработчик, расширяющий описания сообщения за счет подгрузки дополнительных

данных из конфигурационных файлов;

-обработчик, осуществляющий запись сообщения в базу данных;

-обработчик, осуществляющий доставку сообщения потребителям.

-с потоком ассоциируется несколько сокетов. Данная ситуация распространена в задачах интеграции приложений, когда клиенту необходимо для получения результата использовать данные, предоставляемые несколькими серверами. Например, система управления предприятием (клиент) использует для заданного сценария составления квартальной отчетности данные, предоставляемые подсистемой складского учета, подсистемой бухгалтерского учета и использует внешнюю подсистему документооборота для формирования и хранения pdf файла. Другая распространенная модель организации поточной обработки данного типа – наличие фонового потока, связанного с сокетом и очереди для задач определенного типа. Данный подход носит название «единый фоновый процесс».

Подходы "поток-на-задачу" и "единый фоновый поток" могут довольно хорошо функционировать в определенных ситуациях. Подход "поток-на-задачу" хорошо работаeт с небольшим количеством долгосрочных задач. Подход "единый фоновый поток" функционирует довольно хорошо, если не важна предсказуемость распределения (scheduling predictability), как в случае низкоприоритетных фоновых задач. Однако большая часть серверных приложений ориентированы на обработку большого количества краткосрочных задач или подзадач, и нужно иметь механизм для эффективного осуществления этих задач с небольшими издержками, а также какую-либо меру управления ресурсами и предсказуемостью времени выполнения. Для решения этих задач могут использоваться пулы потоков и очереди действий.

  1. Пулы потоков

Работа многих серверных приложений, таких как Web-серверы, серверы базы данных, серверы файлов или почтовые серверы, связана с совершением большого количества коротких задач, поступающих от какого-либо удаленного источника. Запрос прибывает на сервер определенным образом, например, через сетевые протоколы (такие как HTTP, FTP или POP), или, возможно, путем опроса базы данных. Независимо от того, как запрос поступает, в серверных приложениях часто бывает, что обработка каждой индивидуальной задачи кратковременна, а количество запросов большое.

Одной из упрощенных моделей для построения серверных приложений является создание нового потока каждый раз, когда запрос прибывает и обслуживание запроса в этом новом потоке. Этот подход в действительности хорош для разработки прототипа, но имеет значительные недостатки, что стало бы очевидным, если бы вам понадобилось развернуть серверное приложение, работающее таким образом. Один из недостатков подхода "поток-на-запрос" состоит в том, что системные издержки создания нового потока для каждого запроса значительны; a сервер, создавший новый поток для каждого запроса, будет тратить больше времени и потреблять больше системных ресурсов, создавая и разрушая потоки, чем он бы тратил, обрабатывая фактические пользовательские запросы.

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

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

Рассмотрим механизм работы пула потоков. Имеется главный поток приложения, прослушивающий клиентские запросы. Пул потоков создается заранее или при поступлении первого запроса. Минимальный размер пула обычно выбирается равным 1, однако это непринципиально. При поступлении запроса главный поток выбирает поток из пула и передает ему запрос. Если количество потоков в пуле достигло максимума, запрос помещается в очередь. Если количество потоков меньше максимального, и все они заняты обработкой, создается новый поток, который получает клиентский пакет на обработку. Если количество потоков равно максимальному и все потоки занимаются обработкой, то есть активны, пакет ставится в очередь и ждет освобождения одного из потоков. Алгоритмы добавления потоков в пул и определения оптимального размера пула сильно зависят от решаемой задачи.

Рис 11.1 – Пул потоков

Доступ нескольких потоков к одному ресурсу – очереди задач – требует применения синхронизации для избегания проблемы гонки данных. Синхронизированный доступ потоков снижает быстродействие обработки. Чем меньше "вычислительная нагрузка" задач, тем чаще потоки соревнуются за доступ к очереди. Для уменьшения накладных расходов, связанных с необходимостью синхронизации доступа потоков к глобальной очереди, в структуру пула потоков могут быть введены локальные очереди, как это реализовано в .NET 4.0. Каждая локальная очередь соответствует одному рабочему потоку. В таком случае (рис 11.2) пул потоков будет состоять из глобальной очереди, организованной по принципу FIFO, и множества локальных очередей, организованных по принципу LIFO.

Рис 11.2 – Пул потоков в .NET 4.0

Дисциплина очереди FIFO (first-in, first-out) предполагает, что первый добавленный элемент первым поступит на обработку. Рабочие потоки обращаются к глобальной очереди и получают задачу на обработку. Задача, которая попала в локальную очередь, может породить дочернюю и они размещаются в эту же локальную очередь.. Так как только рабочий поток разгребает свою собственную кучу, вернее имеет доступ к ее началу (head), то на уровне локальной очереди не требуется никаких синхронизаций. Поэтому добавление и извлечение задачи из этой очереди происходят очень быстро. Побочным эффектом такого выполнения является то, что очередь разгребается в обратном порядке. Хотя делать какие-то допущения в программе о порядке выполнения задач в очереди нельзя – так как пул потоков своеобразный черный ящик.

Локальные очереди предназначены для вложенных задач. Задачи, которые объявляются и запускаются из пользовательского потока, являются задачами верхнего уровня (top-most level tasks). Такие задачи помещаются в глобальную очередь и распределяются по рабочим потокам. При выполнении задачи верхнего уровня рабочий поток добавляет в свою локальную очередь все вложенные задачи.

Рис 11.3 – Пул потоков в .NET 4.0

Вложенные задачи помещаются в локальную очередь рабочего потока, в котором выполняется родительская задача. Локальные очереди организованы по принципу LIFO (last-in, first-out). Первой вложенной задачей, которая будет выполняться рабочим потоком, будет та задача, которая добавлена в очередь последней. Такой порядок объясняется возможной локальностью данных. Последняя добавленная вложенная задача использует общие данные в ближайшем окружении.

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

Взаимоблокировка

В любом многопоточном приложении есть риск взаимоблокировки. Говорят, что набор процессов или потоков взаимоблокирован, когда каждый ожидает события, которое может быть вызвано другим процессом. Простейший случай взаимоблокировки - когда поток A полностью блокирует объект X и ожидает блокировки объекта Y, в то время как поток B полностью блокирует объект Y и ожидает блокировки объекта X. И если нет какого-либо способа вырваться из ожидания, взаимоблокированные потоки будут ожидать вечно.

Поскольку взаимоблокировка - риск в любой многопоточной программе, пулы потоков предполагают другую возможность взаимоблокировки, где все потоки пулов осуществляют задачи, которые блокируются в ожидании результатов другой задачи в очереди, но эта задача не может запуститься, поскольку нет доступного незанятого потока. Это может случиться, когда пулы потоков используются для проведения имитационных экспериментов, включающих большое количество взаимодействующих объектов, имитационные объекты могут посылать запросы друг другу, которые потом выполняются как задачи из очереди, и запрашиваемый объект синхронно ожидает ответа.

Пробуксовка ресурсов

Одно из преимуществ пулов потоков состоит в том, что они обычно хорошо выполняют операции, имеющие отношение к альтернативным распределяющим механизмам. Но это верно только в том случае, если размер пула потоков настроен правильно. Потоки потребляют многочисленные ресурсы, включая память и другие системные ресурсы. Кроме памяти, каждый поток требует двух списков вызовов выполнения, которые могут быть большими. Поскольку распределяющиеся издержки переключения между потоками малы, для многих потоков переключение процессов может стать значительным замедлением в работе программы.

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

Параллельные ошибки

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

Утечки потоков

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

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

Перегрузка запросами

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

3. Настройка размера пула. Алгоритм адаптации размера пула

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

Есть два основных преимущества в организации поточной обработки сообщений в приложениях: возможность продолжения процесса во время ожидания медленных операций, таких, как I/O (ввод - вывод), и использование возможностей нескольких процессоров. В приложениях с ограничением по скорости вычислений, функционирующих на N-процессорной машине, добавление дополнительных потоков может улучшить пропускную способность, по мере того как количество потоков подходит к N, но добавление дополнительных потоков свыше N не оправдано. Действительно, слишком много потоков разрушают качество функционирования из-за дополнительных издержек переключения процессов

Оптимальный размер пула потоков зависит от количества доступных процессоров и природы задач в рабочей очереди. На N-процессорной системе для рабочей очереди, которая будет выполнять исключительно задачи с ограничением по скорости вычислений, достигается максимального использования CPU с пулом потоков, в котором содержится N или N+1 поток.

Для задач, которые могут ждать осуществления I/O (ввода - вывода) -- например, задачи, считывающей HTTP-запрос из сокета – может понадобиться увеличение размера пула свыше количества доступных процессоров, потому, что не все потоки будут работать все время. Используя профилирование, можно оценить отношение времени ожидания (WT) ко времени обработки (ST) для типичного запроса. Если назвать это соотношение WT/ST, для N-процессорной системе понадобится примерно N*(1+WT/ST) потоков для полной загруженности процессоров. Использование процессора – не единственный фактор, важный при настройке размера пула потоков. По мере возрастания пула потоков, можно столкнуться с ограничениями планировщика, доступной памяти, или других системных ресурсов, таких, как количество сокетов, дескрипторы открытого файла, или каналы связи базы данных

Для регуляций числа рабочих потоков используется также следующий алгоритм. Число потоков контролируется двумя числами:

- нижний уровень – m_lowRatio;

- верхний уровень - m_hiRatio.

В процессе функционирования происходит вычисление соотношения:

ratio= threads /tasks,

где threads- число работающих потоков, а tasks – число заданий в глобальной очереди

В тот момент, когда соотношение ratio возрастает и превышает m_hiRatio, добавляются дополнительные потоки. В тот момент, когда соотношение проходит нижнюю отметку m_lowRatio, происходит удаление избыточных потоков. Создание потока является достаточно ресурсоемкой операцией, поэтому во избежание частой смены числа рабочих потоков должно быть адекватно выбран интервал между верхней и нижней границами отношения числа потоков к размеру очереди m_hiRatio - m_lowRatio. Псевдокод алгоритма приведен в листинге 1.

Листинг - Алгоритм адаптации размера пула

Исходные данные:

int e /*Количество элементов в очереди*/

int alive /*Количество рабочих потоков-нитей*/

Fiber[] m_fibers /*Список рабочих потоков*/

int m_loRatio /*Нижний порог соотношения числа заданий к числу потоков*/

int m_hiRatio /*Верхний порог соотношения числа заданий к числу потоков*/

Переменные:

int ratio; /*Соотношение длины очереди заданий к числу потоков*/

  1. ratio  e / alive

/* Если:

* 1) Нитей больше чем одна...

* 2) отношение меньше, чем нижняя метка

*/

  1. if (alive > 1 && ratio <= m_loRatio)

begin

  1. for (Fiber fiber  m_fibers)

begin

  1. if (fiber != null)

begin

  1. switch ( fiber.getStatus() )

begin

  1. case RUNNING:

  1. f  fiber;

  2. last  f.getStatus();

  3. case STOP_PENDING:

  4. f  null;

last  Fiber.STOP_PENDING;

end

end

  1. if (f != null && f.getStatus() != STOP_PENDING)

begin

  1. f.stop( )

end

/* Если:

* 1a) Нити равны нулю и очереди не пусты, или..

* 1b) отноешние выше чем hiRatio, и...

* 2) Нитей меньше чем максимальный размер */

13. else if (((alive == 0 && e > 0) || ratio > m_hiRatio) && alive < m_maxSize)

begin

14. for (int x  0; x < m_fibers.length; x++)

begin

15. if (m_fibers[x]==null||m_fibers[x].getStatus() ==STOPPED)

begin

16. f  new FiberThreadImpl( );

17. f.start();

18. m_fibers[x]  f;

break;

end

end

end

Подстройка размера пула потоков, основанная на отношении элементов в очереди к потокам. Вычисление данного соотношения производится в строке 1. Пул потоков регулируется верхней и нижней отметками, которые ограничивают размер потоков в очередях. В случае если вычисленное соотношение меньше нижней метки (строка 2), то производится поиск избыточных потоков, что осуществляется в строках 3-12. Для того, чтобы предотвратить избыточное удаление и как следствие перерегулировку, перед удалением производится поиск останавливаемых потоков (STOP_PENDING) (строки 5-10). Если таковые будут обнаружены, удаление не производится.

Соседние файлы в папке Лекции