Пытлив умом был Федор, но не мудр
Не разгадав явление природы
Решил он, что ларек снесли за ночь
В нескольких предыдущих лекциях мы видели, что основная проблема при разработке многопоточных приложений - это обеспечение согласованности разделяемых структур данных. На практике эта проблема решается главным образом при помощи концепций критических секций и взаимоисключения.
Критическая секция -это участок кода программы, вовремя исполнения которого программа либо полагается на целостность разделяемой структуры данных, либо тем или иным образом нарушает эту целостность. Для примера рассмотрим операции над отсортированным массивом без дублирующихся элементов. Основные операции над таким массивом - поиск, вставка и исключение элемента. Поиск в отсортированном массиве удобно осуществлять дихотомическим или, что то же самое, двоичным алгоритмом. Этот алгоритм работает очень быстро, но если условие сортировки по какой-то причине будет нарушено, попытка дихотомического поиска завершится неудачно.
Все способы вставки и исключения элемента в отсортированном массиве предполагают либо [временное] нарушение условия сортировки, либо появление в массиве дубликатов, либо то и другое одновременно. Поэтому все перечисленные операции являются критическими секциями. Для обеспечения нормальной работы этих операций нам необходимо тем или иным способом гарантировать, что они не будут выполняться одновременно над одним и тем же массивом. Это и называется взаимоисключением.
Внимательный читатель может отметить, что в отсортированном массиве очень сложно реализовать параллельное исполнение нескольких вставок или удалений, и практически невозможно реализовать параллельное исполнение вставки и поиска, но несколько поисков вполне могут выполняться одновременно.
Разделяемые данные являются основным средством взаимодействия между нитями. Собственно, если бы нам были не нужны разделяемые данные, мы могли бы использовать процессы вместо нитей.
Главная сложность при определении критической секции состоит в том, что это, строго
говоря, не формализованное понятие. Для каждого конкретного алгоритма можно точно
определить, какие структуры данных с его точки зрения являются согласованными. Но
критические
Существует ряд решений задачи взаимоисключения на нескольких флаговых переменных, например алгоритм Деккера, который состоит в использовании двух флаговых переменных. Каждая из взаимодействующих нитей устанавливает "свой" флаг, а проверяет "чужой".
Этот алгоритм сложно распространить на взаимодействие произвольного количества нитей, поэтому на практике он очень редко применяется.
Большинство современных процессоров в той или иной форме предоставляют атомарную
(неделимую) операцию проверки и установки флага. Например, в процессорах x86 это
команда EXCHG (exchange, обменять), которая обменивает значения регистра и ячейки
памяти.
Единственный разумный способ ожидать освобождения флаговой переменной - это холостой цикл. Исполняющая такой цикл программа занимает процессор, но не делает ничего полезного. Примитив взаимоисключения, использующий проверку флага в холостом цикле и его проверку и установку при помощи специальной атомарной команды процессора, называется спинлоком (spinlock). Спинлоки используются многими операционными системами для взаимодействия между копиями ядра ОС, исполняющимися на разных процессорах многопроцессорной машины, а также в некоторых приложениях жесткого реального времени. POSIX Thread API также предоставляет спинлоки, но поддержка соответствующей группы типов и функций опциональна (не обязательна для всех реализаций). Solaris поддерживает спинлоки POSIX, но в нашем курсе мы их изучать не будем.
Для взаимодействия между пользовательскими процессами и нитями современные ОС и многопоточные среды программирования предоставляют более сложные примитивы, объединяющие проверку флага и засыпание, пробуждение и установку флага и, наконец, сброс флага и пробуждение другой нити в атомарные операции.
Под словом "примитив" мы в данном случае понимаем непрозрачный тип данных, над которым определен фиксированный набор операций.
Мутекс является одним из таких примитивов. Слово мутекс (
Мутекс может находиться в двух состояниях - свободном и захваченном. Над мутексом определены две основные операции - блокировка (lock, захват, asquire) и снятие (unlock, освобождение, release). Блокировка свободного мутекса приводит к его переводу в захваченное состояние. Попытка блокировки захваченного мутекса приводит к засыпанию (блокировке) нити, которая пыталась выполнить эту операцию. Освобождение свободного мутекса - недопустимая операция; в зависимости от особенностей реализации эта операция может приводить к непредсказуемым последствиям или к ошибке или просто игнорироваться. Освобождение занятого мутекса приводит к переводу мутекса в свободное состояние; если в этот момент на мутексе были заблокированы одна или несколько нитей, одна из этих нитей пробуждается и захватывает мутекс.
Главной особенностью мутексов является тот факт, что захват и освобождение мутекса должны производиться одной нитью. Поэтому можно говорить о том, что нить, захватившая мутекс, владеет им.
Применение мутексов для решения задачи взаимоисключения очевидно. Мы должны связать с каждым разделяемым ресурсом мутекс. Когда нить входит в критическую секцию, связанную с ресурсом, она должна захватить мутекс, а когда выходит из нее - освободить.
Мутексы в POSIX Thread API имеют тип pthread_mutex_t. Это непрозрачный тип, операции
над которым должны осуществляться соответствующими функциями библиотеки POSIX
Threads. Внутренняя структура объектов этого типа не документирована и может
различаться в разных реализациях и даже в разных версиях одной реализации POSIX
Threads.
Перед использованием мутекс необходимо инициализировать. Это может делаться
функцией pthread_mutex_init(3C) или присваиванием мутексу константы PTHREAD_MUTEX_INITIALIZER, определенной в <pthread.h>. Функция pthread_mutex_init(3C) получает два параметра, указатель на инициализируемый объект и
указатель на описание атрибутов мутекса, структуру pthread_mutex_attr_t. Все параметры
мутекса задаются в pthread_mutex_attr_t, которая рассматривается далее на этой лекции.
Результат выполнения остальных операций над неинициализированным мутексом не
определен; это может приводить к блокировке нити на неопределенное время или к
аварийному завершению процесса.
Перед освобождением памяти из-под мутекса мутекс необходимо уничтожить. Это
делается функцией pthread_mutex_destroy(3C). Операции над мутексом могут приводить к
размещению дополнительной памяти или объектов ядра ОС, поэтому уничтожение мутекса
без выполнения pthread_mutex_destroy может приводить к утечке памяти или исчерпанию
системных ресурсов. Выполнение операции pthread_mutex_destroy над мутексом, на
котором заблокирована одна или несколько нитей, приводит к неопределенным
последствиям. Дальнейшие операции над этим мутексом также приводят к
неопределенным последствиям.
Над мутексом определены четыре основные операции: pthread_mutex_lock(3C), pthread_mutex_unlock(3C), pthread_mutex_trylock(3C) и pthread_mutex_timedlock(3C).
Семантика операций lock и unlock описывалась в начале лекции; единственное, что следует
отметить - что pthread_mutex_lock(3C), в отличие от большинства других блокирующихся
операций, не является точкой прерывания.
Pthread_mutex_trylock(3C) пытается захватить мутекс; если он занят, операция возвращает
ошибку EAGAIN.
Pthread_mutex_timedlock(3C) - блокировка с тайм-аутом. Эта функция пытается захватить
мутекс и блокируется, если это невозможно, но не дольше чем до указанного момента.
Если функция вернула управление по тайм-ауту, она возвращает ошибку ETIMEOUT.
Solaris предоставляет также функцию pthread_mutex_reltimedlock_np(3C), которая задает
относительный тайм-аут, т.е. интервал времени от момента вызова. Суффикс _np у имени
функции обозначает, что эта функция не входит в стандарт POSIX.
По умолчанию операции над мутексами не осуществляют никаких проверок. При
повторном захвате мутекса той же нитью произойдет мертвая блокировка. Результат
других некорректных последовательностей операций, например захвата мутекса в одной
нити и освобождения в другой, или многократного освобождения, не определен стандартом,
хотя в большинстве распространенных реализаций, в том числе и в Solaris 10, результаты
таких операций обычно достаточно безобидны. Используя pthread_attr_t, при
инициализации мутекса можно задать параметры, которые заставляют систему делать
проверки при работе с мутексами и возвращать ошибки при некорректных
последовательностях операций. Разумеется, операции над мутексами без проверок гораздо
дешевле, чем с проверками.
Групповых операций над мутексами POSIX Thread Library не предоставляет.
При практическом использовании мутексов следует иметь в виду, что мутекс похож на
дорожный светофор: он указывает, что вам не следует пересекать дорогу, но не ставит вам
физических препятствий. Иными словами, если программист по какой-то причине забыл
захватить мутекс перед входом в критическую секцию или неверно определил границы
критической секции, среда программирования сама по себе не сообщит ему об ошибке.
Ошибки такого рода (так называемые ошибки соревнования,
Этот недостаток характерен для всех примитивов межпоточного взаимоисключения и синхронизации. Фактически, он является оборотной стороной того, что потоки разделяют память и никак не защищены друг от друга.
В поставку SunStudio 11 входит инструмент, который может использоваться для обнаружения ошибок соревнования в коде. Этот инструмент предполагает компиляцию и сборку программы со специальными ключами. Компилятор при этом вставляет в код специальные инструкции для сбора статистики обращений к данным. После прогона программы можно просмотреть эту статистику и, в том числе, обнаружить операции над разделяемыми данными, не защищенные мутексом.
При использовании мутексов и большинства других примитивов взаимоисключения
возникает проблема мертвых блокировок ( deadlock ). Иногда мертвые блокировки также
называют тупиками ( dead end ). Простейшая форма мертвой блокировки - это ситуация,
когда одна и та же нить два раза пытается захватить один и тот же мутекс.
В реальном коде такая мертвая блокировка может возникать, например, в следующей ситуации: программист пытается реализовать класс, все методы которого являются Thread- safe. Для этого он включает в структуру данных мутекс и захватывает и освобождает этот мутекс при каждом вызове метода своего объекта.
Затем у программиста возникает потребность в вызове собственного метода (см. пример 1.1):
class monitor {
private:
pthread_mutex_t mx;
public:
int method1()
{ pthread_mutex_lock(mx); … pthread_mutex_unlock(mx); }
int method2()
{
pthread_mutex_lock(mx);
…
method1();
…
pthread_mutex_unlock(mx);
}
}
POSIXThreadLibrary реализует несколько способов борьбы с этой ошибкой, которые рассматриваются далее на этой лекции.
Более сложный - и более опасный - сценарий возникновения такой же ошибки - вызов в
обработчике сигнала функций, которые содержат внутренние блокировки и не являются
Еще более сложный сценарий мертвой блокировки включает в себя минимум две нити и
минимум два ресурса, например A и B. Если одна нить захватывает эти ресурсы в порядке lock(A) ; lock(B), а другая - в порядке lock(B) ; lock(A) ;, при неудачном стечении
обстоятельств нити могут заблокировать друг друга. Такая блокировка может включать в
себя и более чем две нити; формальным критерием такой блокировки является
возникновение цикла в графе ожидающих друг друга нитей.
При использовании мутексов, теоретически можно обнаруживать ошибки такого рода на
уровне библиотеки, выстраивая граф ожидающих друг друга нитей и находя в нем циклы.
На практике, стандарт POSIX не требует выполнения такой проверки даже для мутексов
типа PTHREAD_MUTEX_ERRORCHECK, и все проверенные мной реализации не
осуществляют такой проверки. Таким образом, борьба с мертвыми блокировками,
включающими несколько нитей, оказывается обязанностью программиста.
Два основных способа борьбы с мертвой блокировкой - это упорядочение блокировок и
групповые захваты мутексов. Более радикальные способы включают в себя запрет
удерживать в каждый момент времени более одного мутекса и полный отказ от
разделяемых данных и переход к взаимодействию через трубы или очереди сообщений.
Первый способ слишком радикален для большинства практических применений; второй
способ лишает нас основного преимущества многопоточных программ (возможности
взаимодействовать через разделяемую память) и не будет рассматриваться в этом курсе.
Средств группового захвата мутексов POSIX Thread API не предоставляет, хотя такой
захват можно реализовать самостоятельно, используя pthread_mutex_trylock(3C) и условные
переменные (
Оба эти способа страдают тем недостатком, что они нарушают требования модульности и инкапсуляции. При работе с программным модулем мы должны знать обо всех блокировках, которые этот модуль может установить. Особенно важен этот недостаток при использовании групповых блокировок. Упорядочение блокировок меньше страдает от этого недостатка.
Действительно, большинство реальных приложений и библиотек имеют многослойную структуру; при этом функции и методы "верхних" слоев вызывают функции и методы "нижних" слоев, но вызовы в обратном направлении происходят редко или вообще не происходит. Такая структура приложения создает естественный порядок захвата блокировок, и все, что требуется от программиста - не нарушать этот порядок умышленно, например, удерживая какую-то блокировку между вызовами функций своего слоя.
Впрочем, ряд приемов программирования, таких, как хуки, callback, сигналы, при
определенных обстоятельствах - виртуальные методы и обработка исключений - приводят
к нарушению порядка межслойного взаимодействия и, соответственно, могут привести к
нарушению порядка захвата блокировок. Проявление этой проблемы мы видели на
предыдущей лекции, когда обсуждали сигналы и атрибут Async-Signal-Safe.
Как и pthread_attr_t, pthread_mutex_attr_t представляет собой непрозрачный тип данных,
содержащий набор атрибутов мутекса. Для просмотра и изменения этих атрибутов
необходимо использовать get/set функции. Как и у pthread_attr_t, между атрибутами и
мутексом не образуется никакой постоянной связи. Если завести переменную pthread_attr_t_attr
и создать по ней мутекс, последующие изменения attr не окажут никакого влияния на
уже созданный мутекс. Большинство атрибутов мутекса вообще не могут быть изменены
после создания мутекса.
Список атрибутов мутекса таков:
psharedtypeprotocolprioceilingrobust_np (нестандартный)Атрибут pshared определяет область действия мутекса. Допустимые значения - PTHREAD_PROCESS_SHARED (разделяемый мутекс, который может использоваться для
межпроцессного взаимодействия) и PTHREAD_PROCESS_PRIVATE (локальный или
приватный мутекс, пригодный только для синхронизации нитей одного процесса).
Значение по умолчанию - PTHREAD_PROCESS_PRIVATE. Чтобы использовать
разделяемый мутекс для межпроцессного взаимодействия, его необходимо разместить в
сегменте разделяемой памяти, например в файле, отображенном на память с флагом MAP_SHARED, или в разделяемой памяти
Атрибут type обозначает способ проверки ошибок при работе с мутексом. Допустимые
значения - PTHREAD_MUTEX_NORMAL, PTHREAD_MUTEX_ERRORCHECK, PTHREAD_MUTEX_RECURSIVE и PTHREAD_MUTEX_DEFAULT. Мутексы типа NORMAL не делают никаких проверок. Многократный захват мутекса одной нитью
приводит к мертвой блокировке, результаты многократного освобождения мутекса или
захвата мутекса одной нитью и освобождения другой не определены. На практике, в Solaris
10 и в Linux попытки освобождения свободного мутекса игнорируются, а попытка
освобождения мутекса, захваченного другой нитью, приводит к тому, что мутекс
переводится в состояние "свободен" и одна из заблокированных на нем нитей
освобождается. Таким образом, в некоторых реализациях мутексы типа NORMAL ведут
себя скорее как двоичные семафоры, чем как мутексы в строгом смысле этого слова. Но
стандарт POSIX не гарантирует, что мутексы будут вести себя именно таким образом во
всех реализациях, в том числе и в будущих версиях Linux и Solaris.
Тип ERRORCHECK требует, чтобы все операции над мутексами проверяли состояние
мутекса и возвращали ошибки при недопустимых последовательностях операций над
мутексом. Из описания pthread_attr_settype(3C) можно сделать вывод, что такие мутексы
делают проверку на мертвую блокировку с участием нескольких нитей, но это не так. В
соответствии с требованиями стандарта, код ошибки EDEADLK возвращается только при
попытке захвата мутекса, уже занятого текущей нитью. Мутексы типа ERRORCHECK
удобны для отладки приложений, но требуют гораздо большего объема вычислений, чем
мутексы типа NORMAL.
Мутексы типа RECURSIVE допускают многократный захват одной и той же нитью. Такой
мутекс поддерживает счетчик захватов и должен быть освобожден нитью столько же раз,
сколько был захвачен. Такие мутексы удобны для борьбы с мертвыми блокировками,
возникающими в коде, аналогичном примеру 1. Попытка освобождения свободного
мутекса или мутекса, занятого другой нитью, приводит к ошибке.
Тип DEFAULT в соответствии со стандартом делает даже меньше проверок, чем тип NORMAL. Так, повторный захват мутекса типа NORMAL приводит к мертвой блокировке,
а повторный захват мутекса типа DEFAULT, в соответствии со стандартом, может
приводить к непредсказуемым последствиям вплоть до аварийного завершения процесса.
Реализация имеет право отображать тип DEFAULT на любой другой тип. Solaris 10
отображает тип DEFAULT на тип NORMAL.
Атрибут protocol описывает схему предотвращения PTHREAD_PRIO_NONE, т.е. не предоставляют никаких средств для борьбы с
protocol - это PTHREAD_PRIO_INHERIT и PTHREAD_PRIO_PROTECT. INHERIT обозначает
наследование приоритета: приоритет нити, удерживающей мутекс, повышается до самого
высокого из приоритетов нитей, ждущей этого мутекса. PROTECT обозначает так
называемый "потолок приоритета" (priority ceiling): приоритет процесса, удерживающего
мутекс, равен приоритету, указанному в свойствах этого мутекса, а именно - в свойстве prioceiling. Приоритет мутекса вообще говоря не зависит от приоритетов нитей, ждущих на
этом мутексе. Если нить удерживает несколько мутексов с протоколами INHERIT и PROTECT, ее приоритет будет равен наивысшему из приоритетов, обеспечиваемых этими
мутексами.
Атрибут prioceiling используется мутексами с протоколом PTHREAD_PRIO_PROTECT.
При остальных значениях атрибута protocol этот атрибут игнорируется.
Атрибут robust_np используется главным образом с межпроцессными мутексами
( pshared==PROCESS_SHARED ). Это нестандартный атрибут, реализованный в Solaris; в
Linux этот атрибут появился в версии ядра 2.6 и NPTL; большинство других реализаций
POSIX Threads API его не поддерживают. Суффикс _np у имени атрибута сигнализирует,
что этот атрибут не является частью стандарта POSIX. Как ни странно, в соответствии со
страницей pthread_attr_setrobust_np(3C) системного руководства Solaris 10, для
использования атрибута robust_np необходимо установить атрибут protocol равным POSIX_PRIO_INHERIT, а pshared может принимать любое значение.
Атрибут robust_np управляет поведением мутекса в ситуации, когда владелец этого мутекса
аварийно завершился. Под аварийным завершением подразумевается не pthread_cancel(3С),
а завершение процесса по сигналу. Завершение процесса по сигналу приводит к
завершению всех нитей этого процесса; локальные мутексы и защищаемые ими ресурсы
при этом уничтожаются и их судьба никого не интересует. Однако при использовании
разделяемой памяти и разделяемых мутексов возможна ситуация, когда один из процессов
завершается, а остальные продолжают работу. Если одна из нитей завершившегося
процесса удерживала мутекс, то нити остальных процессов, пытающиеся захватить этот
мутекс, могут остаться в состоянии бесконечного ожидания. Эта проблема не может быть
решена за счет принудительного освобождения мутекса, ведь занятость мутекса
сигнализирует, что защищаемый им ресурс, скорее всего, находится в несогласованном
состоянии.
Атрибут robust_np может принимать два значения: PTHREAD_MUTEX_STALLED_NP и PTHREAD_MUTEX_ROBUST_NP. Значение по умолчанию - PTHREAD_MUTEX_STALLED_NP. При этом мутексы, удерживавшиеся завершившимся
процессом, просто остаются в занятом состоянии, и все нити, пытающиеся захватить их,
блокируются.
В режиме (надежный) мутекс реализует более сложное поведение. Первая нить,
пытающаяся захватить мутекс, получает ошибку EOWNERDEAD, но мутекс при этом
захватывается. Нить должна попытаться привести ресурс, защищаемый мутексом, в
согласованное состояние. Если это удается, нить должна вызвать функцию pthread_mutex_consistent_np(3C). После этого остальные нити смогут продолжить
нормальную работу с мутексом и ресурсом. Если восстановить согласованное состояние
ресурса не удается, нить должна просто освободить мутекс. После этого все попытки
захватить такой мутекс будут завершаться с кодом ошибки ENOTRECOVERABLE.
Гэндальф: Это Балрог! Бежим. Гимли: Но мы все равно не можем бежать быстрее демона! Леголас: Нам достаточно бежать быстрее тебя!
В системах с
Один из наглядных примеров этой проблемы можно наблюдать, если редактировать
большой документ в текстовом процессоре Microsoft Word 2000 с включенным
автосохранением. Проблема воспроизводится только если вставлять текст в начало или в
середину документа, вынуждая Word переразбивать текст на страницы. При сохранении
документа Word должен завершить разбиение на страницы, а для этого он вынужден
остановить редактирование, иначе есть риск, что переразбиение на страницы никогда не
завершится. Таким образом, при редактировании большого документа с включенным
автосохранением пользователь большую часть времени взаимодействует с
высокоприоритетным и действительно быстро реагирующим потоком, отвечающим за
редактирование - но иногда оказывается вынужден ждать завершения работы
низкоприоритетного фонового потока. Это выглядит как внезапное "зависание" Word. Если
в интерактивных приложениях
При расчете времени реакции на событие, разработчик системы реального времени должен принимать во внимание не только время исполнения кода, непосредственно обрабатывающего это событие, но и времена работы всех критических секций во всех нитях, которые могут удерживать мутексы, необходимые обработчику - ведь обработчик не сможет продолжить исполнение, пока не захватит эти мутексы, а произвольно снимать их нельзя, потому что они сигнализируют, что защищаемый ими разделяемый ресурс находится в несогласованном состоянии.
Если высокоприоритетная нить пытается захватить мутекс, занятый низкоприоритетной нитью, то в определенном смысле получится, что эта нить будет работать со скоростью низкоприоритетного процесса.
В условиях, когда планировщик не обеспечивает справедливого распределения времени центрального процессора, а в системе наравне с высокоприоритетной нитью (работа которой нас интересует) и низкоприоритетной (которая держит мутекс) существуют еще среднеприоритетные нити, может оказаться, что низкоприоритетная нить не будет получать управления в течение значительного времени. На это же время будет заблокирована и высокоприоритетная нить.
Особенно серьезна эта проблема, когда высоко- и низкоприоритетная нити относятся к
разным
При разработке ПО для бортового компьютера космического аппарата Mars Pathfinder, была принята архитектура, которую сами разработчики называли "общей шиной" - глобальная разделяемая область памяти, которую все процессы в системе использовали для коммуникации. Доступ к этой области защищался мутексом.
В системе существовала высокоприоритетная нить, занимавшаяся управлением шиной, которая периодически запускалась по сигналам таймера и собирала из "шины" некоторую важную для нее информацию. Эта же нить должна была сбрасывать сторожевой таймер, подтверждая, что система функционирует нормально.
Кроме того, в системе существовало еще несколько нитей, осуществлявших доступ к шине, в частности нить сбора метеорологических данных. Эта нить также запускалась по расписанию и копировала данные в буфер "шины", разумеется, захватывая при этом мутекс.
Таким образом, если нить управления "шиной" запускалась во время работы "метеорологической" нити, то она должна была ждать некоторое дополнительное время. В моменты, когда в системе не было других активных нитей, это не приводило к проблемам.
Однако когда накладывалось исполнение трех нитей, интервал ожидания сильно
увеличивался, и это приводило к срабатыванию
При тестировании на Земле сбросы такого рода несколько раз происходили, но разработчики не смогли воспроизвести условия их возникновения (срабатывание ошибки требует наложения трех нитей, каждая из которых сама по себе большую часть времени заблокирована) и отправили на Марс аппарат с недиагностированной проблемой. При эксплуатации непредсказуемые и происходящие, в среднем, раз в несколько дней сбросы бортового компьютера доставляли много неудобств центру управления полетом; к тому же, специалисты центра вынуждены были объяснять происходящее журналистам. На копии системы на Земле сбросы долгое время не удавалось воспроизвести; наконец, под утро, когда в лаборатории оставался только один разработчик, сброс все-таки произошел и по анализу отладочного дампа системы проблему удалось решить.
Основным средством борьбы с
Более радикальное решение называется потолком приоритета (priority ceiling). Оно состоит в том, что приоритет нити, удерживающей мутекс, приравнивается наивысшему из приоритетов нитей, которые могут захватить этот мутекс. Разумеется, во время исполнения определить потолок приоритета невозможно, он должен устанавливаться программистом как параметр мутекса.
Легко понять, что во время работы в критических секциях, защищенных такими мутексами, задачи - даже низкоприоритетные - должны подчиняться всем ограничениям, которые может накладывать исполнение в режиме реального времени. Если мы в каком-то смысле не доверяем низкоприоритетной нити (в частности, не можем оценить время, в течение которого она будет удерживать мутекс), нам следует отказаться от использования разделяемой памяти для взаимодействия с этой нитью и перейти к каким-либо буферизованным средствам обмена данными. При этом достаточно обеспечить гарантированное время передачи данных в буфер; для того, чтобы избежать переполнения буфера, достаточно того, чтобы средний темп генерации данных высокоприоритетной нитью был ниже среднего же возможного темпа их обработки низкоприоритетным потребителем. Впрочем, если темп такой обработки подвержен большим флуктуациям (например, из-за наличия в системе других процессов), может оказаться необходимо предусмотреть возможность сброса части данных для борьбы с переполнением буфера - именно так борются с перегрузками сетевые маршрутизаторы и коммутаторы.
Необходимо отметить, что и наследование, и потолок приоритета применимы только к
мутексам и другим примитивам синхронизации, для которых можно указать, какая именно
нить их удерживает. Точнее говоря, для борьбы с
Поэтому даже те системы, которые предоставляют наследование или потолки приоритетов для мутексов, не предоставляют аналогичных средств для семафоров-счетчиков. В книге [QNX 2004] приводятся результаты экспериментов над системой реального времени QNX, из которых видно, что эта ОС реализует наследование приоритетов на мутексах, но при использовании семафоров-счетчиков можно получить классический случай инверсии приоритета.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.