Легковесные процессы и синхронизация


Содержание

Легковесные процессы и синхронизация

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

Воспользуемся следующим неформальным определением. Будем понимать под процессом последовательность действий, составляющих некоторое вычисление, которая характеризуется:

  • сопоставленной ему программой/подпрограммой, то есть упорядоченной последовательностью операций, реализующих действия, которые должны осуществляться процессом;
  • содержимым соответствующей ему памяти, то есть множеством данных, которыми этот процесс может манипулировать;
  • дескриптором процесса, то есть совокупностью сведений, определяющих состояние ресурсов, предоставленных процессу.

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

Полновесные процессы ( tasks — задачи ) — это процессы, выполняющиеся внутри защищенных участков памяти операционной системы, то есть имеющие собственные виртуальные адресные пространства для статических и динамических данных. В мультипрограммной среде управление такими процессами тесно связанно с управлением и защитой памяти, поэтому переключение процессора с выполнения одного процесса на выполнение другого является дорогой операцией.

Легковесные процессы ( threads — нити ), называемые еще сопроцессами, не имеют собственных защищенных областей памяти. Они работают в мультипрограммном режиме одновременно с активировавшей их задачей и используют ее виртуальное адресное пространство, в котором им при создании выделяется участок памяти под динамические данные (стек), то есть они могут обладать собственными локальными данными. Сопроцесс описывается как обычная функция, которая может использовать статические данные программы.

Понятие легковесного процесса появилось не так давно, и за ним еще не закрепился достаточно удачный термин. Прямой перевод thread как нить (управления) не кажется хорошим названием. Поэтому мы используем термин сопроцесс для обозначения легковесного процесса. Такое название указывает на то, что это процесс, причем в чем-то специфический, сосуществующий одновременно с другим процессом. Не следует путать сопроцесс с сопрограммой, хотя это достаточно близкие понятия. В механизме сопрограмм необходимо явно с помощью соответствующего оператора передавать управление от одной сопрограммы к другой. Сопрограмма вызывается подобно подпрограмме, но в отличие от подпрограммы каждый вызов (кроме первого) сопрограммы возобновляет е§ выполнение с точки последнего возврата (оператора, непосредственно следующего за оператором обращения к другой сопрограмме).

Devprom ALM: Руководство пользователя

Легковесные процессы разработки

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

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

Поиск или создание продукта

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

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

Управление проектом на стадии поиска и создания продукта осуществляется на основе Scrum.

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

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

Из-за меняющихся требований приходится переделывать то, что было реализовано на предыдущих итерациях — это потеря времени.

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

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

Необходимы условия для обильной коммуникации пользователей (представителя) и членов команды.

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

Развитие продукта

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

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

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

Пользовательские требования будущего релиза управляются процессом на основе Scrum. Здесь, как и в процессе «Поиск и создание продукта», осуществляется планирование спринтов, декомпозиция требований на задачи, демонстрация и приемка реализованной функциональности. Из-за того, что часть усилий команды будет оттягивать текущий релиз, требования к срокам (или составу) спринтов смягчаются.

Плюсы
Требования

Те же, что у процесса «Поиск и создание продукта»

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

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

Те же, что у процесса «Поиск и создание продукта»

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

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

Необходимы условия для обильной коммуникации пользователей (представителя) и членов команды.

Упрощенный процесс реализации ошибок и мелких доработок (текущий релиз) требует от исполнителей большей квалификации и кросс-функциональности, чтобы не ухудшить качество продукта.

Сопровождение продукта

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

В этом процессе нет спринтов (итераций) и отсутствует пакетная реализация. При использовании современых технологических решений, выпуск новых версий, обновлений или патчей, может осуществляться буквально под каждое пользовательское требование (подробнее об этом можно узнать в подходе Continuous Delivery).

Доска историй (или пожеланий) отражает всю технологическую цепочку. Каждый статус (столбец на доске) соответствует одному из этапов технологии разработки: анализу, проектированию, разработке, тестированию и т.п. За каждым этапом закреплен один или несколько членов команды. Каждое пользовательское требование последовательно проходит все этапы, начиная от анализа и заканчивая документированием. Это напоминает традиционный водопадный процесс, с тем отличием, что скоуп каждого этапа сокращен до одного пользовательского требования.

С точки зрения управления проектом, в данном процессе используется подход Software Kanban. Это простой процесс из бережливого производства (Lean Management). Есть суть заключается в следующем:

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

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

Плюсы
Требования

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

Поскольку скорость решения запросов примерно одинакова (или может быть сегментирована по классам задач: срочные, доработки, ошибки), можно эффективно оценивать сроки и стоимость оставшегося объема работ.

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

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

Задачи не должны сильно различаться в размере. Если это происходит, скорее всего вы имеете дело со стадией «Развитие продукта» и процесс нужно менять.

Jdk12-doc zip. Последние новости о самой передовой технологии программирования смотрите на странице

Плюсы
Требования
Название Jdk12-doc zip. Последние новости о самой передовой технологии программирования смотрите на странице
страница 12/23
Тип Документы

rykovodstvo.ru > Руководство эксплуатация > Документы


Глава 11

Легковесные процессы и синхронизация

Параллельное программирование, связанное с использованием легковесных процессов, или подпроцессов (multithreading, light-weight processes) — концептуальная парадигма, в которой вы разделяете свою программу на два или несколько процессов, которые могут исполняться одновременно.

Во многих средах параллельное выполнение заданий представлено в том виде, который в операционных системах называется многозадачностью. Это совсем не то же самое, что параллельное выполнение подпроцессов. В многозадачных операционных системах вы имеете дело с полновесными процессами, в системах с параллельным выполнением подпроцессов отдельные задания называются легковесными процессами (light-weight processes, threads).

Цикл обработки событий в случае единственного подпроцесса

В системах без параллельных подпроцессов используется подход, называемый циклом обработки событий. В этой модели единственный подпроцесс выполняет бесконечный цикл, проверяя и обрабатывая возникающие события. Синхронизация между различными частями программы происходит в единственном цикле обработки событий. Такие среды называют синхронными управляемыми событиями системами. Apple Macintosh, Microsoft Windows, X11/Motif — все эти среды построены на модели с циклом обработки событий.

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

Модель легковесных процессов в Java

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

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

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

Коль скоро вы разделили свою программу на логические части — подпроцессы, вам нужно аккуратно определить, как эти части будут общаться друг с другом. Java предоставляет для этого удобное средство — два подпроцесса могут “общаться” друг с другом, используя методы wait и notify. Работать с параллельными подпроцессами в Java несложно. Язык предоставляет явный, тонко настраиваемый механизм управления созданием подпроцессов, переключения контекстов, приоритетов, синхронизации и обмена сообщениями между подпроцессами.

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

public static void main(String args[]) <

Thread t = Thread.currentThread();

System.out. println(«current thread: » + t);

catch (InterruptedException e) <

В этом примере текущий подпроцесс хранится в локальной переменной t. Затем мы используем эту переменную для вызова метода setName, который изменяет внутреннее имя подпроцесса на “My Thread”, с тем, чтобы вывод программы был удобочитаемым. На следующем шаге мы входим в цикл, в котором ведется обратный отсчет от 5, причем на каждой итерации с помощью вызова метода Thread.sleep() делается пауза длительностью в 1 секунду. Аргументом для этого метода является значение временного интервала в миллисекундах, хотя системные часы на многих платформах не позволяют точно выдерживать интервалы короче 10 миллисекунд. Обратите внимание — цикл заключен в try/catch блок. Дело в том, что метод Thread.sleep() может возбуждать исключение InterruptedException. Это исключение возбуждается в том случае, если какому-либо другому подпроцессу понадобится прервать данный подпроцесс. В данном примере мы в такой ситуации просто выводим сообщение о перехвате исключения. Ниже приведен вывод этой программы:

С:\> java CurrentThreadDemo

current thread: Thread[My Thread,5,main]

Обратите внимание на то, что в текстовом представлении объекта Thread содержится заданное нами имя легковесного процесса — My Thread. Число 5 — это приоритет подпроцесса, оно соответствует приоритету по умолчанию, “main” — имя группы подпроцессов, к которой принадлежит данный подпроцесс.

Не очень интересно работать только с одним подпроцессом, а как можно создать еще один? Для этого нам понадобится другой экземпляр класса Thread. При создании нового объекта Thread ему нужно указать, какой программный код он должен выполнять. Вы можете запустить подпроцесс с помощью любого объекта, реализующего интерфейс Runnable. Для того, чтобы реализовать этот интерфейс, класс должен предоставить определение метода run. Ниже приведен пример, в котором создается новый подпроцесс.

class ThreadDemo implements Runnable <

Thread ct = Thread.currentThread();

Thread t = new Thread(this, «Demo Thread»);

System.out.println(«Thread created: » + t);

catch (InterruptedException e) <

Реферат: Новые возможности операционных систем

Эффективное использование легковесных процессов в симметричных мультипроцессорах

Пользовательские легковесные процессы

Методология применения легковесных процессов

Современные файловые системы

Ограничения традиционных файловых систем

Распространенные файловые системы

Файловые системы с журнализацией

Эффективное использование легковесных процессов в симметричных мультипроцессорах

Поддерживаемые в современных операционных системах (в частности, в ОС UNIX) понятия нити (thread), потока управления, или легковесного процесса на самом деле появились и получили реализацию около 30 лет тому назад. Наиболее известной операционной системой, ориентированной на поддержку множественных процессов, которые работают в общем адресном пространстве и с общими прочими ресурсами, была легендарная ОС Multics. Эта операционная система заслуживает длительного отдельного обсуждения, но, естественно не в данном курсе. Мы рассмотрим (в общих чертах) особенности легковесных процессов в современных вариантах операционной системы UNIX. По всей видимости, все или почти все содержимое этого раздела можно легко отнести к любой операционной системе, поддерживающей легковесные процессы. Несмотря на различия в терминологии, в различных реализациях легковесных процессов выделяются три класса. Но прежде, чем перейти к рассмотрению этих классов, обсудим общую природу процесса в ОС UNIX.

Контекст процесса

Каждому процессу соответствует контекст, в котором он выполняется. Этот контекст включает содержимое пользовательского адресного пространства — пользовательский контекст (т.е. содержимое сегментов программного кода, данных, стека, разделяемых сегментов и сегментов файлов, отображаемых в виртуальную память), содержимое аппаратных регистров — регистровый контекст (таких, как регистр счетчика команд, регистр состояния процессора, регистр указателя стека и регистров общего назначения), а также структуры данных ядра (контекст системного уровня), связанные с этим процессом. Контекст процесса системного уровня в ОС UNIX состоит из «статической» и «динамических» частей. У каждого процесса имеется одна статическая часть контекста системного уровня и переменное число динамических частей.

Статическая часть контекста процесса системного уровня включает следующее:

A.Описатель процесса, т.е. элемент таблицы описателей существующих в системе процессов. Описатель процесса включает, в частности, следующую информацию:

  • состояние процесса;
  • физический адрес в основной или внешней памяти u-области процесса;
  • идентификаторы пользователя, от имени которого запущен процесс;
  • идентификатор процесса;
  • прочую информацию, связанную с управлением процессом.

B. U-область (u-area), индивидуальная для каждого процесса область пространства ядра, обладающая тем свойством, что хотя u-область каждого процесса располагается в отдельном месте физической памяти, u-области всех процессов имеют один и тот же виртуальный адрес в адресном пространстве ядра. Именно это означает, что какая бы программа ядра не выполнялась, она всегда выполняется как ядерная часть некоторого пользовательского процесса, и именно того процесса, u-область которого является «видимой» для ядра в данный момент времени. U-область процесса содержит:

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

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

Ядерные нити

Базовым классом являются ядерные нити. В мире UNIX это не новость. Когда в пользовательском процессе происходит системный вызов или прерывание, выполняется ядерная составляющая пользовательского процесса в своем собственном контексте, включающем набор ядерных стеков и регистровое окружение. Естественно, все ядерные составляющие пользовательских процессов работают в общем адресном пространстве с общим набором ресурсов ядра. Поэтому их вполне можно назвать ядерными легковесными процессами. Наличие ядерных нитей, в частности, облегчает обработку прерываний в режиме ядра. Как и в случае прерывания обычного пользовательского процесса, обработка прерывания ядерной нити производится в ее контексте, и после возврата из прерывания продолжается выполнение прерванной ядерной нити. Кроме того, каждая ядерная нить, вообще говоря, обладает собственным приоритетом по отношению к праву выполняться на процессоре (конечно, этот приоритет связан с приоритетом соответствующего пользовательского процесса). Это позволяет использовать гибкую политику планирования процессорных ресурсов для ядерных составляющих. Итак, ядерные нити должны существовать независимо от того, поддерживаются ли легковесные процессы в режиме пользователя. Наверное, трудно найти сегодня какую-либо многопользовательскую операционную систему, в ядре которой в каком-то виде не поддерживались бы нити.

Пользовательские легковесные процессы

Видимо, следующим по важности классом легковесных процессов являются пользовательские LWP (LightWeight Processes). Механизмы этого рода позволяют пользователям организовать несколько потоков управления в одном адресном пространстве. Все LWP одного пользовательского процесса совместно используют все ресурсы процесса. При поступлении процессу сигнала на этот сигнал реагируют все LWP в соответствии со своими собственными установками. С другой стороны, каждый LWP обладает своим собственным контекстом, включающим, как и в случае ядерных нитей, стек и регистровое окружение (в частности, содержимое индивидуального счетчика команд). Любому LWP пользовательского процесса соответствует отдельная ядерная нить. Это означает, что каждый LWP может отдельно планироваться (и поэтому LWP одного пользовательского процесса могут параллельно выполняться на разных процессорах симметричного мультипроцессорного компьютера), и системные вызовы и прерывания LWP могут обрабатываться независимо. Основным преимуществом использования LWP является возможность достижения реального распараллеливания программы при ее выполнении на симметричном мультипроцессоре (на недостатках мы остановимся ниже).

Пользовательские нити

Наконец, к третьему классу легковесных процессов относятся пользовательские нити. Они называются пользовательскими, поскольку реализуются не ядром ОС, а с помощью специальной библиотеки функций (поэтому, например, в ОС Mach их называют C-Threads). Это тоже очень старая идея, к использованию которой неоднократно прибегали все опытные программисты (здесь уже даже не важно, в среде какой операционной системы выполняется программа). Суть идеи состоит в том, что вся программа пользователя строится в виде набора сопрограмм (coroutine), которые выполняются под управлением общего монитора. Естественно, что в мониторе поддерживаются контексты всех сопрограмм, но и монитор, и сопрограммы являются составляющими одного пользовательского процесса, которому соответствует одна ядерная нить. Конечно, с использованием пользовательских нитей невозможно достичь реального распараллеливания программы. Единственный реальный эффект, которого можно добиться, состоит в возможности распараллеливания обменов при использовании асинхронного режима системных вызовов. Как считают некоторые специалисты (к числу которых не относится автор этой части курса), основное достоинство использования пользовательских нитей состоит в лучшей структуризации программы.

Методология применения легковесных процессов

Мы проследили достоинства разных видов легковесных процессов и можем перейти к краткому анализу их недостатков. Многие программисты (включая автора статьи) испытали эти недостатки на себе. При программировании с явным использованием техники легковесных процессов возникает потребность в явной синхронизации по отношению к общим ресурсам. В современных вариантах ОС UNIX имеется несколько разновидностей средств синхронизации: блокировки, семафоры, условные переменные. Но в любом случае, механизм синхронизации является явным, оторванным от ресурса, для доступа к которому производится синхронизация. Если у легковесных процессов одного пользовательского процесса общих ресурсов немного, то такую программу написать и отладить сравнительно несложно. Но при наличии большого количества общих ресурсов отладка программы становится очень сложным делом (даже при использовании только пользовательских нитей).


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

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

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

С другой стороны, системные программисты с успехом используют новые средства. При наличии большого желания, времени и денег параллельную программу можно надежно отладить, что демонстрирует большинство компаний, разрабатывающих программное обеспечение баз данных. При этом используются и LWP, поддерживаемые операционной системой, и пользовательские нити. Как кажется (это мнение не только автора), использование LWP безусловно оправдано, если сервер БД конфигурируется в расчете на работу на симметричном мультипроцессоре. На сегодняшний день это единственная возможность реально распараллелить работу сервера. Но совершенно неочевидно, что применение пользовательских нитей при разработке сервера в целях его структуризации (а это делается во многих серверах), является лучшим решением. Известны другие методы структуризации, которые, по меньшей мере, не менее удобны.

Современные файловые системы

Ограничения традиционных файловых систем

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

На практике наиболее распространена последовательная работа с файлами. Если требуется произвести последовательный просмотр файла, хранящегося на магнитном диске с подвижными головками, то основные задержки создают именно электро-механическом действия, связанные с перемещением магнитных головок. Известно, что в среднем время движения головок на два порядка превышает время двух последующих актов — ожидания подкрутки диска до нужного блока (тоже долго, но не так) и собственно выполнения обмена (это как раз достаточно быстро, и чем дальше, тем быстрее по мере развития технологии магнитных носителей; к сожалению заставить быстро двигаться пакеты магнитных головок не так легко).

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

Появились устройства с магнитными дисками, предъявляющие внешнему миру интерфейс с 24 магнитными головками в то время, когда на самом деле (физически) их было 15. И что же теперь оптимизируется? Аппаратура и встроенное программное обеспечение контроллеров магнитных дисков сами производят собственную оптимизацию, а файловая система считает, что все уже оптимизировано. Еще хуже дела стали с появлением дисковых массивов, особенно начиная с пятого уровня организации. RAID обеспечивает надежное хранение данных с 90-процентной гарантией и разделенное хранение блока данных на всех дисках, входящих в массив. Что же теперь оптимизируют операционная система по отношению к доступу к файлам? Это одна из основных проблем современных файловых систем; она не решена, и не ясно, будет ли решена в ближайшее время. Тем не менее, файловые системы развиваются, и мы далее приведем обзор наиболее интересных существующих файловых систем (из мира UNIX) и некоторые примеры перспективных файловых систем.

Распространенные файловые системы

Понятие файла является одним из наиболее важных для ОС UNIX. Все файлы, с которыми могут манипулировать пользователи, располагаются в файловой системе, представляющей собой дерево, промежуточные вершины которого соответствуют каталогам, и листья — файлам и пустым каталогам. Реально на каждом логическом диске (разделе физического дискового пакета) располагается отдельная иерархия каталогов и файлов. Для получения общего дерева в динамике используется «монтирование» отдельных иерархий к фиксированной корневой файловой системе.

Замечание: в мире ОС UNIX по историческим причинам термин «файловая система» является перегруженным, обозначая одновременно иерархию каталогов и файлов и часть ядра, которая управляет каталогами и файлами. Видимо, было бы правильнее называть иерархию каталогов и файлов архивом файлов, а термин «файловая система» использовать только во втором смысле. Однако, следуя традиции ОС UNIX, мы будем использовать этот термин в двух смыслах, различая значения по контексту.

Каждый каталог и файл файловой системы имеет уникальное полное имя (в ОС UNIX это имя принято называть full pathname — имя, задающее полный путь, посколько оно действительно задает полный путь от корня файловой системы через цепочку каталогов к соответствующему каталогу или файлу; мы будем использовать термин «полное имя», поскольку для pathname отсутствует благозвучный русский аналог). Каталог, являющийся корнем файловой системы (корневой каталог) в любой файловой системе имеет предопределенное имя «/» (слэш). Полное имя файла, например, /bin/sh означает, что в корневом каталоге должно содержаться имя каталога bin, а в каталоге bin должно содержаться имя файла sh. Коротким или относительным именем файла (relative pathname) называется имя (возможно, составное), задающее путь к файлу начиная от текущего рабочего каталога (существует команда и соответствующий системный вызов, позволяющие установить текущий рабочий каталог.

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

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

  • cp имя1 имя2 — копирование файла имя1 в файл имя2
  • rm имя1 — уничтожение файла имя1
  • mv имя1 имя2 — переименование файла имя1 в файл имя2
  • mkdir имя — создание нового каталога имя
  • rmdir имя — уничтожение каталога имя
  • ls имя — выдача содержимого каталога имя
  • cat имя — выдача на экран содержимого файла имя
  • chown имя режим — изменение режима доступа к файлу

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

В мире UNIX существует несколько разных видов файловых систем со своей структурой внешней памяти. Наиболее известны традиционная файловая система UNIX System V (s5) и файловая система семейства UNIX BSD (ufs). Файловая система s5 состоит из четырех секций (рисунок 6.1(a)). В файловой системе ufs на логическом диске (разделе реального диска) находится последовательность секций файловой системы.

Кратко опишем суть и назначение каждой области диска:

    Boot-блок содержит программу раскрутки, которая служит для первоначального запуска ОС UNIX. В файловых системах s5 реально используется boot-блок только корневой файловой системы. В дополнительных файловых системах эта область присутствует, но не используется.

  • Суперблок — это наиболее ответственная область файловой системы, содержащая информацию, которая необходима для работы с файловой системой в целом. Суперблок содержит список свободных блоков и свободные i-узлы (information nodes — информационные узлы). В файловых системах usf для повышения устойчивости поддерживается несколько копий суперблока (как видно из рисунка 6.1(b), по одной копии на группу цилиндров). Каждая копия суперблока имеет размер 8196 байт, и только одна копия суперблока используется при монтировании файловой системы (см. ниже). Однако, если при монтировании устанавливается, что первичная копия суперблока повреждена или не удовлетворяет критериям целостности информации, используется резервная копия.
  • Блок группы цилиндров содержит число i-узлов, специфицированных в списке i-узлов для данной группы цилиндров, и число блоков данных, которые связаны с этими i-узлами. Размер блока группы цилиндров зависит от размера файловой системы. Для повышения эффективности файловая система ufs старается выделять i-узлы и блоки данных в одной и той же группе цилиндров.
  • Список i-узлов (ilist) содержит список i-узлов, соответствующих файлам данной файловой системы. Максимальное число файлов, которые могут быть созданы в файловой систем, определяется числом доступных i-узлов. В i-узле хранится информация, описывающая файл: режимы доступа к файлу, время создания и последней модификации, идентификатор пользователя и идентификатор группы создателя файла, описание блочной структуры файла и т.д.
  • Блоки данных — в этой части файловой системы хранятся реальные данные файлов. В случае файловой системы ufs все блоки данных одного файла пытаются разместить в одной группе цилиндров. Размер блока данных определяется при форматировании файловой системы командой mkfs и может быть установлен в 512, 1024, 2048, 4096 или 8192 байт.

Файлы любой файловой системы становятся доступными только после «монтирования» этой файловой системы. Файлы «не смонтированной» файловой системы не являются видимыми операционной системой.

Для монтирования файловой системы используется системный вызов mount. Монтирование файловой системы означает следующее. В имеющейся к моменту монтирования дереве каталогов и файлов должен иметься листовой узел — пустой каталог (в терминологии UNIX такой каталог, используемый для монтирования файловой системы, называется directory mount point — точка монтирования). В любой файловой системе имеется корневой каталог. Во время выполнения системного вызова mount корневой каталог монтируемой файловой системы совмещается с каталогом — точкой монтирования, в результате чего образуется новая иерархия с полными именами каталогов и файлов.

Смонтированная файловая система впоследствии может быть отсоединена от общей иерархии с использованием системного вызова umount. Для успешного выполнения этого системного вызова требуется, чтобы отсоединяемая файловая системы к этому моменту не находилась в использовании (т.е. ни один файл из этой файловой системы не был открыт). Корневая файловая система всегда является смонтированной, и к ней не применим системный вызов umount.

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

Ядро ОС UNIX поддерживает для работы с файлами несколько системных вызовов. Среди них наиболее важными являются open, creat, read, write, lseek и close.

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

Файл в системных вызовах, обеспечивающих реальный доступ к данным, идентифицируется своим дескриптором (целым значением). Дескриптор файла выдается системными вызовами open (открыть файл) и creat (создать файл). Основным параметром операций открытия и создания файла является полное или относительное имя файла. Кроме того, при открытии файла указывается также режим открытия (только чтение, только запись, запись и чтение и т.д.) и характеристика, определяющая возможности доступа к файлу:

open(pathname, oflag [,mode])

Одним из признаков, могущих участвовать в параметре oflag, является признак O_CREAT, наличие которого указывает на необходимость создания файла, если при выполнении системного вызова open файл с указанным именем не существует (параметр mode имеет смысл только при наличии этого признака). Тем не менее по историческим причинам и для обеспечения совместимости с предыдущими версиями ОС UNIX отдельно поддерживается системный вызов creat, выполняющий практически те же функции.

Открытый файл может использоваться для чтения и записи последовательностей байтов. Для этого поддерживаются два системных вызова:

read(fd, buffer, count) и write(fd, buffer, count)

Здесь fd — дескриптор файла (полученный при ранее выполненном системном вызове open или creat), buffer — указатель символьного массива и count — число байтов, которые должны быть прочитаны из файла или в него записаны. Значение функции read или write — целое число, которое совпадает со значением count, если операция заканчивается успешно, равно нулю при достижении конца файла и отрицательно при возникновении ошибок.

В каждом открытом файле существует текущая позиция. Сразу после открытия файл позиционируется на первый байт. Другими словами, если сразу после открытия файла выполняется системный вызов read (или write), то будут прочитаны (или записаны) первые count байт содержимого файла (конечно, они будут успешно прочитаны только в том случае, если файл реально содержит по крайней мере count байт). После выполнения системного вызова read (или write) указатель чтения/записи файла будет установлен в позицию count+1 и т.д.

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

lseek(fd, offset, origin)

Как и раньше, здесь fd — дескриптор ранее открытого файла. Параметр offset задает значение относительного смещения указателя чтения/записи, а параметр origin указывает, относительно какой позиции должно применяться смещение. Возможны три значения параметра origin. Значение 0 указывает, что значение offset должно рассматриваться как смещение относительно начала файла. Значение 1 означает, что значение offset является смещением относительно текущей позиции файла. Наконец, значение 2 говорит о том, что задается смещение относительно конца файла. Заметим, что типом данных параметра offset является long int. Это значит, что, во-первых, могут задаваться достаточно длинные смещения и, во-вторых, смещения могут быть положительными и отрицательными.

Например, после выполнения системного вызова

указатель чтения/записи соответствующего файла будет установлен на начало (на первый байт) файла. Системный вызов

установит указатель на конец файла. Наконец, выполнение системного вызова

приведет к увеличению текущего значения указателя на 10.

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

Файловые системы с журнализацией

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

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

Файловые системы с журнализацией разделяются на две категории: системы, производящие журнализацию всех изменений, и системы, журнализующие только изменения метаданных. Среди систем второй категории выделяются те, которые журнализуют только некоторую выделенную информацию (например, не помещают в журнал информацию о смене владельца файла).

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

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

Имеется два типа журналов: журнал, ориентированный только на повторное выполнение операций (redo-only), и журнал, способный поддерживать как повторное выполнение операций, так и их обратное выполнение (undo-redo). В журнале «undo-redo» сохраняются как новые, так и старые значения данных. При использовании журнала типа «redo-only» операции восстановления упрощаются, но требуется ограничивать порядок записи метаданных в журнал и на место их постоянного хранения. Журнал «undo-redo» больше по объему и требует применения более сложного механизма журнализации, но использование этого типа журнализации допускает более высокий уровень параллельности.

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

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

Наиболее известной файловой системой, основанной исключительно на журнализации и журнализующей все изменения, является BSD-LFS (UNIX BSD 4.4 Log-Structured File System). Среди файловых систем, поддерживающих журнализацию только метаданных, можно выделить Cedar, Calaveras и Veritas.


Синхронизация процессов при работе с Windows

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

Главной идеей, заложенной в основе синхронизации потоков в Win32, является использование объектов синхронизации и функций ожидания. Объекты могут находиться в одном из двух состояний — Signaled или Not Signaled. Функции ожидания блокируют выполнение потока до тех пор, пока заданный объект находится в состоянии Not Signaled. Таким образом, поток, которому необходим эксклюзивный доступ к ресурсу, должен выставить какой-либо объект синхронизации в несигнальное состояние, а по окончании — сбросить его в сигнальное. Остальные потоки должны перед доступом к этому ресурсу вызвать функцию ожидания, которая позволит им дождаться освобождения ресурса.

Рассмотрим, какие объекты и функции синхронизации предоставляет нам Win32 API.

Функции синхронизации

Функции синхронизации делятся на две основные категории: функции, ожидающие единственный объект, и функции, ожидающие один из нескольких объектов.

Функции, ожидающие единственный объект

Простейшей функцией ожидания является функция WaitForSingleObject:

Функция ожидает перехода объекта hHandle в сигнальное состояние в течение dwMilliseconds миллисекунд. Если в качестве параметра dwMilliseconds передать значение INFINITE, функция будет ждать в течение неограниченного времени. Если dwMilliseconds равен 0, то функция проверяет состояние объекта и немедленно возвращает управление.

Функция возвращает одно из следующих значений:

Название: Новые возможности операционных систем
Раздел: Рефераты по информатике, программированию
Тип: реферат Добавлен 03:33:02 12 марта 2002 Похожие работы
Просмотров: 176 Комментариев: 13 Оценило: 4 человек Средний балл: 5 Оценка: неизвестно Скачать
Поток, владевший объектом, завершился, не переведя объект в сигнальное состояние
Объект перешел в сигнальное состояние
Истек срок ожидания. Обычно в этом случае генерируется ошибка либо функция вызывается в цикле до получения другого результата
Произошла ошибка (например, получено неверное значение hHandle). Более подробную информацию можно получить, вызвав GetLastError

Следующий фрагмент кода запрещает доступ к Action1 до перехода объекта ObjectHandle в сигнальное состояние (например, таким образом можно дожидаться завершения процесса, передав в качестве ObjectHandle его идентификатор, полученный функцией CreateProcess):

В случае когда одновременно с ожиданием объекта требуется перевести в сигнальное состояние другой объект, может использоваться функция SignalObjectAndWait:

Возвращаемые значения аналогичны функции WaitForSingleObject.

! В модуле Windows.pas эта функция ошибочно объявлена как возвращающая значение BOOL. Если вы намерены ее использовать – объявите ее корректно или используйте приведение типа возвращаемого значения к DWORD.

Объект hObjectToSignal может быть семафором, событием (event) либо мьютексом. Параметр bAlertable определяет, будет ли прерываться ожидание объекта в случае, если операционная система запросит у потока окончание операции асинхронного ввода-вывода либо асинхронный вызов процедуры. Более подробно это будет рассматриваться ниже.

Функции, ожидающие несколько объектов

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

Функция возвращает одно из следующих значений:

Число в диапазоне от

Если bWaitAll равно TRUE, то это число означает, что все объекты перешли в сигнальное состояние. Если FALSE — то, вычтя из возвращенного значения WAIT_OBJECT_0, мы получим индекс объекта в массиве lpHandles

Число в диапазоне от

Если bWaitAll равно TRUE, это означает, что все объекты перешли в сигнальное состояние, но хотя бы один из владевших ими потоков завершился, не сделав объект сигнальным Если FALSE — то, вычтя из возвращенного значения WAIT_ABANDONED_0, мы получим в массиве lpHandles индекс объекта, при этом поток, владевший этим объектом, завершился, не сделав его сигнальным Истек период ожидания Произошла ошибка

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

Описанную выше технику можно применять, если вы точно знаете, что задержка ожидания объекта будет незначительной. В противном случае ваша программа окажется «замороженной» и не сможет даже перерисовать свое окно. Если период задержки может оказаться значительным, то необходимо дать программе возможность реагировать на сообщения Windows. Выходом может стать использование функций с ограниченным периодом ожидания (и повторный вызов — в случае возврата WAIT_TIMEOUT) либо функции MsgWaitForMultipleObjects:

Главное отличие этой функции от предыдущей — параметр dwWakeMask, который является комбинацией битовых флагов QS_XXX и задает типы сообщений, прерывающих ожидание функции независимо от состояния ожидаемых объектов. Например, маска QS_KEY позволяет прервать ожидание при появлении в очереди сообщений WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP или WM_SYSKEYDOWN, а маска QS_PAINT — сообщения WM_PAINT. Полный список значений, допустимых для dwWakeMask, имеется в документации по Windows SDK. При появлении в очереди потока, вызвавшего функцию, сообщений, соответствующих заданной маске, функция возвращает значение WAIT_OBJECT_0 + nCount. Получив это значение, ваша программа может обработать его и снова вызвать функцию ожидания. Рассмотрим пример с запуском внешнего приложения (необходимо, чтобы на время его работы вызывающая программа не реагировала на ввод пользователя, однако ее окно должно продолжать перерисовываться):

Если в потоке, вызывающем функции ожидания, явно (функцией CreateWindow) или неявно (используя TForm, DDE, COM) создаются окна Windows — поток должен обрабатывать сообщения. Поскольку широковещательные сообщения посылаются всем окнам в системе, то поток, не обрабатывающий сообщения, может вызвать взаимоблокировку (система ждет, когда поток обработает сообщение, поток — когда система или другие потоки освободят объект) и привести к зависанию Windows. Если в вашей программе имеются подобные фрагменты, необходимо использовать MsgWaitForMultipleObjects или MsgWaitForMultipleObjectsEx и позволять прервать ожидание для обработки сообщений. Алгоритм аналогичен вышеприведенному примеру.

Прерывание ожидания по запросу на завершение операции ввода-вывода или APC

Windows поддерживает асинхронные вызовы процедур. При создании каждого потока (thread) с ним ассоциируется очередь асинхронных вызовов процедур (APC queue). Операционная система (или приложение пользователя — при помощи функции QueueUserAPC) может помещать в нее запросы на выполнение функций в контексте данного потока. Эти функции не могут быть выполнены немедленно, поскольку поток может быть занят. Поэтому операционная система вызывает их, когда поток вызывает одну из следующих функций ожидания:

Если параметр bAlertable равен TRUE (либо если dwFlags в функции MsgWaitForMultipleObjectsEx содержит MWMO_ALERTABLE), то при появлении в очереди APC запроса на асинхронный вызов процедуры операционная система выполняет вызовы всех имеющихся в очереди процедур, после чего функция возвращает значение WAIT_IO_COMPLETION.

Такой механизм позволяет реализовать, например, асинхронный ввод-вывод. Поток может инициировать фоновое выполнение одной или нескольких операций ввода-вывода функциями ReadFileEx или WriteFileEx, передав им адреса функций-обработчиков завершения операции. По завершении вызовы этих функций будут поставлены в очередь асинхронного вызова процедур. В свою очередь, инициировавший операции поток, когда он будет готов обработать результаты, может, используя одну из вышеприведенных функций ожидания, позволить операционной системе вызвать функции-обработчики. Поскольку очередь APC реализована на уровне ядра ОС, она более эффективна, чем очередь сообщений, и позволяет реализовать гораздо более эффективный ввод-вывод.

Объекты синхронизации

Объектами синхронизации называются объекты Windows, идентификаторы которых могут использоваться в функциях синхронизации. Они делятся на две группы: объекты, использующиеся только для синхронизации, и объекты, которые используются в других целях, но могут вызывать срабатывание функций ожидания. К первой группе относятся:

Event (событие)

Event позволяет известить один или несколько ожидающих потоков о наступлении события. Event бывает:

Будучи установленным в сигнальное состояние, остается в нем до тех пор, пока не будет переключен явным вызовом функции ResetEvent

Автоматически переключается в несигнальное состояние операционной системой, когда один из ожидающих его потоков завершается

Для создания объекта используется функция CreateEvent:

Если не требуется задание особых прав доступа под Windows NT или возможности наследования объекта дочерними процессами, в качестве параметра lpEventAttributes можно передавать NIL. В этом случае объект не может наследоваться дочерними процессами и ему задается дескриптор защиты «по умолчанию».

Параметр lpName позволяет разделять объекты между процессами. Если lpName совпадает с именем уже существующего объекта типа Event, созданного текущим или любым другим процессом, то функция не создает нового объекта, а возвращает идентификатор уже существующего. При этом игнорируются параметры bManualReset, bInitialState и lpSecurityDescriptor. Проверить, был ли объект создан или используется уже существующий, можно следующим образом:

Если объект используется для синхронизации внутри одного процесса, его можно объявить как глобальную переменную и создавать без имени.

Имя объекта не должно совпадать с именем любого из существующих объектов типов Semaphore, Mutex, Job, Waitable Timer или FileMapping. В случае совпадения имен функция возвращает ошибку.

Если известно, что Event уже создан, для получения доступа к нему можно вместо CreateEvent воспользоваться функцией OpenEvent:

Функция возвращает идентификатор объекта либо 0 — в случае ошибки. Параметр dwDesiredAccess может принимать одно из следующих значений:

Приложение получает полный доступ к объекту

Приложение может изменять состояние объекта функциями SetEvent и ResetEvent

Только для Windows NT — приложение может использовать объект только в функциях ожидания

После получения идентификатора можно приступать к его использованию. Для этого имеются следующие функции:

— устанавливает объект в сигнальное состояние

— сбрасывает объект, устанавливая его в несигнальное состояние

— устанавливает объект в сигнальное состояние, дает отработать всем функциям ожидания, ожидающим этот объект, а затем снова сбрасывает его.

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

По завершении работы с объектом он должен быть уничтожен функцией CloseHandle.

Delphi предоставляет класс TEvent, инкапсулирующий функциональность объекта Event. Класс расположен в модуле SyncObjs.pas и объявлен следующим образом:

Назначение методов очевидно следует из их названий. Использование этого класса позволяет не вдаваться в тонкости реализации вызываемых функций Windows API. Для простейших случаев объявлен еще один класс с упрощенным конструктором:

Mutex (Mutually Exclusive)


Мьютекс — это объект синхронизации, который находится в сигнальном состоянии только тогда, когда не принадлежит ни одному из процессов. Как только хотя бы один процесс запрашивает владение мьютексом, он переходит в несигнальное состояние и остается таким до тех пор, пока не будет освобожден владельцем. Такое поведение позволяет использовать мьютексы для синхронизации совместного доступа нескольких процессов к разделяемому ресурсу. Для создания мьютекса используется функция:

Функция возвращает идентификатор созданного объекта либо 0. Если мьютекс с заданным именем уже был создан, возвращается его идентификатор. В этом случае функция GetLastError вернет код ошибки ERROR_ALREDY_EXISTS. Имя не должно совпадать с именем уже существующего объекта типов Semaphore, Event, Job, Waitable Timer или FileMapping.

Если неизвестно, существует ли уже мьютекс с таким именем, программа не должна запрашивать владение объектом при создании (то есть должна передать в качестве bInitialOwner значение FALSE).

Если мьютекс уже существует, приложение может получить его идентификатор функцией OpenMutex:

Параметр dwDesiredAccess может принимать одно из следующих значений:

Приложение получает полный доступ к объекту
Только для Windows NT — приложение может использовать объект только в функциях ожидания и функции ReleaseMutex

Функция возвращает идентификатор открытого мьютекса либо 0 — в случае ошибки. Мьютекс переходит в сигнальное состояние после срабатывания функции ожидания, в которую был передан его идентификатор. Для возврата в несигнальное состояние служит функция ReleaseMutex:

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

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

Разумеется, если работа с ресурсом может потребовать значительного времени, то необходимо либо использовать функцию MsgWaitForSingleObject, либо вызывать WaitForSingleObject в цикле с нулевым периодом ожидания, проверяя код возврата. В противном случае ваше приложение окажется замороженным. Всегда защищайте захват-освобождение объекта синхронизации при помощи блока try . finally, иначе ошибка во время работы с ресурсом приведет к блокированию работы всех процессов, ожидающих его освобождения.

Semaphore (семафор)

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

Для создания семафора служит функция CreateSemaphore:

Функция возвращает идентификатор созданного семафора либо 0, если создать объект не удалось.

Параметр lMaximumCount задает максимальное значение счетчика семафора, lInitialCount задает начальное значение счетчика и должен быть в диапазоне от 0 до lMaximumCount. lpName задает имя семафора. Если в системе уже есть семафор с таким именем, то новый не создается, а возвращается идентификатор существующего семафора. В случае если семафор используется внутри одного процесса, можно создать его без имени, передав в качестве lpName значение NIL. Имя семафора не должно совпадать с именем уже существующего объекта типов event, mutex, waitable timer, job или file-mapping.

Идентификатор ранее созданного семафора может быть также получен функцией OpenSemaphore:

Параметр dwDesiredAccess может принимать одно из следующих значений:

Поток получает все права на семафор

Поток может увеличивать счетчик семафора функцией ReleaseSemaphore

Только для Windows NT — поток может использовать семафор в функциях ожидания

Для увеличения счетчика семафора используется функция ReleaseSemaphore:

Если значение счетчика после выполнения функции превысит заданный для него функцией CreateSemaphore максимум, то ReleaseSemaphore возвращает FALSE и значение семафора не изменяется. В качестве параметра lpPreviousCount можно передать NIL, если это значение нам не нужно.

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

Легковесный процесс

Wikipedia open wikipedia design.

В компьютерной операционной системе, легковесный процесс является средством достижения многозадачности, в традиционном понимании этого термина. В Unix System V и Solaris, легковесный процесс работает в пространстве пользователя поверх одного потока выполнения ядра, разделяет виртуальное адресное пространство и системные ресурсы потока выполнения с другими легковесными процессами, в рамках того же процесса. Несколько потоков пользовательского уровня, управляемые с помощью библиотеки потоков, могут быть размещены в одном или нескольких легковесных процессах, что даёт многозадачность на уровне пользователя, которая может иметь некоторые преимущества в производительности [1] .

В некоторых операционных системах нет отдельного слоя легковесных процессов между потоками ядра и пользовательскими потоками. Это означает, что пользовательские потоки реализуются непосредственно потоками ядра. В таких случаях термин «легковесный процесс», как правило, означает поток ядра, а термин «поток» может означать пользовательский поток. В ядре Linux, пользовательские потоки реализованы так, что позволяют определенным процессам совместно использовать ресурсы, что иногда позволяет применять к этим потокам термин «легковесные процессы» [2] . Аналогично в SunOS версии 4 (предшественнице Solaris) легковесными процессами назывались пользовательские потоки [1] .

Содержание

Потоки ядра [ править | править код ]

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

Производительность [ править | править код ]

Создание легковесного процесса обходится дороже и выполняется дольше, чем пользовательский поток. При создании легковесного процесса сначала делается системный вызов, создающий соответствующий поток ядра [ прояснить ] , то есть производится переключение в режим ядра. Такие переключения режимов, как правило, связаны с копированием параметров между ядром и пользовательским пространством, также ядро обычно выполняет дополнительные действия по проверке корректности параметров. Переключение контекста между легковесными процессами требует упреждающего сохранения регистров, затем производится переход в режим ядра, где затем производится сохранение регистров потока ядра, и после того, как производится всё необходимое по планированию легковесного процесса, производится восстановление соответствующих значений регистров в режиме ядре и в пользовательском режиме. [1]

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

Активация планировщика [ править | править код ]

Пока пользовательская библиотека занимается планированием пользовательских потоков, ядро занимается планированием подлежащих легковесных процессов. Без координации между ядром и потоковой библиотекой ядро может принять суб-оптимальные решения планирования. Кроме того, это может привести [Взаимная блокировка|взаимной блокировке], когда пользовательские потоки распределённые по несколько легковесным процессам, пытаются получить те же ресурсы, которые используются другим пользовательским потоком, который не выполняется в данный момент. [1]

Одним из решений этой проблемы является активация планировщика. Это метод координации ядра и библиотеки потоков. Ядро уведомляет планировщик потоковой библиотеки о некоторых событиях (например, когда планируется блокировка потока) и библиотека потоков может принять решение о том, что следует предпринять. Такой уведомляющий вызов от ядра называется «upcall».

Библиотека пользовательского уровня не имеет никакого контроля над механизмом более высокого уровня, она только получает уведомления от ядра и осуществляет планирование потоков пользовательских потоков на имеющихся легковесных процессах, а не процессорах. Планировщик ядра решает как планировать легковесные процессы на процессорах. Это означает что легковесные процессы представляются в библиотеке потоков как «виртуальные процессоры» [3] .

Поддержка в операционных системах [ править | править код ]

В Solaris реализован отдельный слой легковесных процессов, начиная с версии 2.2. До версии 9, Solaris предоставляла многие-ко-многим соотношения легковесных процессов к пользовательским потокам. Однако, это было упразднено, из-за сложностей, к которым это приводило, что также улучшило производительность планировщика ядра [1] .

UNIX System V и его современные производные, такие как IRIX, SCO OpenServer, HP-UX и IBM AIX предоставляют многие-ко-многим сопоставления между пользовательскими потоками и легковесными процессами [3] [4] .

Примечания [ править | править код ]

  1. 123456Юреш Вахалия. Нити и легковесные процессы // UNIX изнутри = UNIX Internals — The New Frontiers / пер. с англ. Е. Васильев, Л. Серебрякова. — Петербург: Питер, 2003. — С. 97. — 844 с. — ISBN 5-94723-013-5, 0-13-101908-2.
  2. Д. Бовет, М. Чезати. Процессы, облегчённые процессы и потоки // Ядро Linux = Understanding the Linux Kernel / пер. с англ. Сергей Иноземцев. — Петербург: БХВ-Петербург, 2007. — С. 123. — 1104 с. — ISBN 0-596-00565-2, 978-5-94157-957-0.
  3. 12Silberschatz, Galvin, Gagne. Chapter 5 — Threads // Operating System Concepts with Java. — 6. — John Wiley & Sons, Inc., 2004. — ISBN 978-0-470-50949-4.
  4. ↑AIX 6.1 — Thread tuning(неопр.) . IBM (2009). Дата обращения 5 декабря 2015.

This page is based on a Wikipedia article written by contributors (read/edit).
Text is available under the CC BY-SA 4.0 license; additional terms may apply.
Images, videos and audio are available under their respective licenses.

Национальная библиотека им. Н. Э. Баумана
Bauman National Library

Персональные инструменты

Синхронизация процессов

Синхронизация процессов (от древне-греч. σύγχρονος — одновременный) — приведение двух или нескольких процессов к такому их протеканию, когда определённые стадии разных процессов совершаются в определённом порядке, либо одновременно.

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

Содержание

Необходимость в синхронизации

Необходимость в синхронизации возникает не только в многопроцессорных системах, но для любого вида параллельных процессов; даже в системах с одним процессором. Некоторые из основных предпосылок для введению синхронизации:


  • Ветвление — Задача разбивается на n-подзадач, которые выполняются n-заданиям. После выполнения, каждая подзадача ждет пока остальные завешат вычисления, затем происходит слияние.
  • Производитель-потребитель — в отношениb производитель-потребитель, процесс-потребитель а зависит от процесса-производителя и ожидает пока необходимые данные будут произведены.
  • Эксклюзивное использование ресурсов — В случае, когда несколько процессов зависят от некоего ресурса и должны получить доступ в одно и то же время, [[Операционная система|ОС должна гарантировать, что только один процесс обращается к ней в данный момент времени, что снижает параллелизм.

Трудности

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

Некоторые другме трудности, которые предстоит решить при синхронизации процессов:

  • Порядок совершения действий;
  • Взаимная блокировка;
  • Ресурсный голод;
  • Инверсия приоритетов;
  • Нагруженное ожидание.

Порядок совершения действий

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

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

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

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

Практически об устранении взаимных блокировок надо заботиться ещё на этапе проектирования системы — это единственный более-менее надежный способ с ними бороться. В крайнем случае, когда основная концепция не допускает возможности избежать взаимных блокировок, следует хотя бы строить все запросы ресурсов так, чтобы такие блокировки безболезненно снимались. Жизненный пример такой ситуации: двое встречаются лицом к лицу. Каждый из них пытается посторониться, но они не расходятся, а несколько секунд сдвигаются в одну и ту же сторону.

Ресурсный голод

ситуация, в которой некий процесс ждет, чтобы получить доступ к ресурсу, который монопольно занят другим процессом (постоянный отказ в необходимых ресурсах). Причиной отказа в ресурсах может быть: • ошибка в алгоритме распределения ресурсов; • утечка ресурсов ; • DoS-атака . Часто причиной отказа в ресурсах может быть слишком простой алгоритм распределения ресурсов. Например, если планировщик всегда предоставляет ресурс потока с высоким приоритетом, то при достаточной нагрузке потоки с низким приоритетом не получат ресурс никогда. И, если поток с более высоким приоритетом зависит от результата работы потока с низким приоритетом, то он не сможет завершить задачу несмотря на свой приоритет. Это называется инверсия приоритетов .

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

Инверсия приоритетов

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

  • Отключение всех прерываний для защиты критических секций
  • Максимизация приоритетов. (A priority ceiling)
  • Наследование приоритетов

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

Нагруженное ожидание

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

Классические проблемы синхронизации

Некоторые классические проблемы синхронизации:

  • Задача поставщика-потребителя;
  • Взаимная блокировка (т.н. deadlock и livelock);
  • задача о читателях-писателях;
  • Проблема обедающих философов;

Задача поставщика-потребителя

Задача поставщика-потребителя ( англ. Producer-consumer problem), также известная как задача ограниченного буфера ( англ. Bounded-buffer problem ) — это классический пример задачи синхронизации нескольких процессов . Задача описывает два процесса, поставщик и потребитель, которые совместно используют буфер установленного размера. Задачей поставщика является создание фрагмента данных, запись его в буфер и повторение этих действий раз за разом. Одновременно с этим потребитель потребляет данные (то есть, удаляет их из буфера) по одному фрагменту за раз. Задача состоит в том, чтобы не дать поставщику записать данные, когда буфер полон, а потребителю не дать удалить данные из пустого буфера.

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

Задача может быть обобщена на случай многих поставщиков и потребителей.

Задача о читателях-писателях

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

« Есть область памяти, позволяющая чтение и запись. Несколько потоков имеют к ней доступ, при этом одновременно могут читать сколько угодно потоков, но писать — только один. Как обеспечить такой режим доступа? »

Первая задача о читателях-писателях (приоритет читателя)

« Пока память открыта на чтение, давать читателям беспрепятственный доступ. Писатели могут ждать сколько угодно. »

Вторая задача о читателях-писателях (приоритет писателя)

« Как только появился хоть один писатель, читателей больше не пускать. При этом читатели могут простаивать. »

Третья задача о читателях-писателях (честное распределение ресурсов)

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

Задача обедающих философов

Задача обедающих философов (англ. Dining philosophers problem) — классический пример, используемый в информатике для иллюстрации проблем синхронизации при разработке параллельных алгоритмов и техник решения этих проблем. Сформулирована в 1965 году Эдсгером Дейкстрой как экзаменационное упражнение для студентов. В качестве примера был взят конкурирующий доступ к ленточному накопителю. Вскоре проблема была сформулирована Ричардом Хоаром в том виде, в каком она известна сегодня.

Постановка задачи

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

Каждый философ может либо есть, либо размышлять. Приём пищи не ограничен количеством оставшейсяся пасты — подразумевается бесконечный запас. Тем не менее, философ может есть только тогда, когда держит две вилки — взятую справа и слева (альтернативная формулировка проблемы подразумевает миски с рисом и палочки для еды вместо тарелок со спагетти и вилок). Каждый философ может взять ближайшую вилку (если она доступна), или положить — если он уже держит её. Взятие каждой вилки и возвращение её на стол являются раздельными действиями, которые должны выполняться одно за другим.

Суть проблемы заключается в том, чтобы разработать модель поведения (параллельный алгоритм), при котором ни один из философов не будет голодать, то есть будет вечно чередовать приём пищи и размышления.

Аппаратная синхронизация

Многие системы обеспечивают аппаратную поддержку для критических секций кода.

Горутины — легковесные процессы

Чтобы просмотреть это видео, включите JavaScript и используйте веб-браузер, который поддерживает видео в формате HTML5

Разработка веб-сервисов на Go — основы языка

Half Faded Star

Go (golang) — современный язык программирования, предназначенный для разработки высококонкурентных приложений, работающих на многопроцессорных системах. Курс даст основы программирования на языке Go, а так же опыт применения языка в основных задачах, которые встречаются сегодня в серверной веб-разработке. В данной части курса будут рассмотрены основы языка и разработки веб-сервисов с использованием стандартной библиотеки. Это курс предназначен для людей с опытом в веб-программировании. Если вы пишете на PHP/Python/Ruby/JS (Node.js) и хотите освоить Go — этот курс для вас. Начинающим программистам может быть немного сложно, т.к. в лекциях используется профессиональных жаргон (сленг), без детальных пояснений. Курс не рассчитан на людей без опыта программирования.

Рецензии

Half Faded Star

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


Очень хороший курс, интересные домашние задания, толковые и информативные лекции. Всем кто интересуется языком Go, Я рекомендую!

Легковесные процессы и синхронизация

Легковесные процессы и синхронизация

Параллельное программирование, связанное с использованием легковесных процессов, или подпроцессов (multithreading, light-weight processes) — концептуальная парадигма, в которой вы разделяете свою программу на два или несколько процессов, которые могут исполняться одновременно.

Во многих средах параллельное выполнение заданий представлено в том виде, который в операционных системах называется многозадачностью. Это совсем не то же самое, что параллельное выполнение подпроцессов. В многозадачных операционных системах вы имеете дело с полновесными процессами, в системах с параллельным выполнением подпроцессов отдельные задания называются легковесными процессами (light-weight processes, threads).

Цикл обработки событий в случае единственного подпроцесса

В системах без параллельных подпроцессов используется подход, называемый циклом обработки событий. В этой модели единственный подпроцесс выполняет бесконечный цикл, проверяя и обрабатывая возникающие события. Синхронизация между различными частями программы происходит в единственном цикле обработки событий. Такие среды называют синхронными управляемыми событиями системами. Apple Macintosh, Microsoft Windows, X11/Motif — все эти среды построены на модели с циклом обработки событий.

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

Модель легковесных процессов в Java

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

Легковесные процессы и синхронизация

Легковесные процессы и синхронизация

Параллельное программирование, связанное с использованием легковесных процессов, или подпроцессов (multithreading, light-weight processes) — концептуальная парадигма, в которой вы разделя

Во многих средах параллельное выполнение заданий представлено в том виде, который в операционных системах называется многозадачностью. Это совсем не то же самое, что параллельное выполнение подпроцессов. В многозадачных операционных сис

Цикл обработки событий в случае единственного подпроцесса

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

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

Модель легковесных процессов в Java

Исполняющая система Java в многом зависит от использования подпроцессов, и все ее классовые библиотеки написаны с учетом особенностей программирования в условиях параллельного выполнения по

Приоритеты подпроцессов — это просто целые числа в диапазоне от 1 до 10 и имеет смысл только соотношения приоритетов различных подпроцессов. Приоритеты же используются для того, чтобы решить, когда нужно остановить один подпроцесс и

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

Коль скоро вы разделили свою программу на логические части — подпроцессы, вам нужно аккуратно определить, как эти части будут общаться друг с другом. Java предоставляет для этого удобное средство — два подпроцесса могут “общаться” д

Класс Thread инкапсулирует все средства, которые могут вам потребоваться при работе с подпроцессами. При запуске Java-программы в ней уже есть один выполняющийся подпроцесс. Вы всегда может

public static void main(String args[]) <

Thread t = Thread.currentThread();

System.out. println(«current thread: » + t);

for (int n = 5; n > 0; n—) <

catch (InterruptedException e) <

В этом примере текущий подпроцесс хранится в локальной переменной t. Затем мы используем эту переменную для вызова метода setName, который изменяет внутреннее имя подпроцесса на “My Thread”, с

С:\> java CurrentThreadDemo

current thread: Thread[My Thread,5,main]

Обратите внимание на то, что в текстовом представлении объекта Thread содержится заданное нами имя легковесного процесса — My Thread. Число 5 — это приоритет подпроцесса, оно соответствует прио

Не очень интересно работать только с одним подпроцессом, а как можно создать еще один? Для этого нам понадобится другой экземпляр класса Thread. При создании нового объекта Thread ему нужно указат

class ThreadDemo implements Runnable <

Thread ct = Thread.currentThread();

Thread t = new Thread(this, «Demo Thread»);

System.out.println(«Thread created: » + t);

catch (InterruptedException e) <

System.out.println(«exiting main thread»);

public void run() <

for (int i = 5; i > 0; i—) <

catch (InterruptedException e) <

System.out.println(«exiting child thread»);

public static void main(String args[]) <

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

С:\> java ThreadDemo

Thread created: Thread[Demo Thread,5,main]

exiting main thread

exiting child thread

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

class Clicker implements Runnable <

private Thread t;

private boolean running = true;

public clicker(int p) <

t = new Thread(this);

public void run() <


public void stop() <

public void start() <

public static void main(String args[]) <

clicker hi = new clicker(Thread.NORM_PRIORITY + 2);

clicker lo = new clicker(Thread.NORM_PRIORITY — 2);

catch (Exception e) <

System.out.println(lo.click + » vs. » + hi.click);

По значениям, фигурирующим в распечатке, можно заключить, что подпроцессу с низким приоритетом достается меньше на 25 процентов времени процессора:

304300 vs. 4066666

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

У каждого Java-объекта есть связанный с ним неявный монитор, а для того, чтобы войти в него, надо вызвать метод этого объекта, отмеченный ключевым словом synchronized

void call(String msg) <

class Caller implements Runnable <

public Caller(Callme t, String s) <

public void run() <

public static void main(String args[]) <

Callme target = new Callme();

new Caller(target, «Hello.»);

new Caller(target, «Synchronized»);

new Caller(target, «World»);

Вы можете видеть из приведенного ниже результата работы программы, что sleep в методе call приводит к переключению контекста между подпроцессами, так что вывод наших 3 строк-сообщений п

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

В Java имеется элегантный механизм общения между подпроцессами, основанный на методах wait, notify и

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

synchronized int get() <

synchronized void put(int n) <

System.out. println(«Put: » + n);

class Producer implements Runnable <

new Thread(this, «Producer»).start();

public void run() <

class Consumer implements Runnable <

new Thread(this, «Consumer»).start();

public void run() <

public static void main(String args[]) <

Хотя методы put и get класса Q синхронизованы, в нашем примере нет ничего, что бы могло помешать поставщику переписывать данные по того, как их получит потребитель, и наоборот, потребителю ниче

Как видите, после того, как поставщик помещает в переменную n значение 1, потребитель начинает работать и извлекает это значение 5 раз подряд. Положение можно исправить, если поставщик будет пр

Правильным путем для получения того же результата в Java является использование вызовов wait и notify для передачи сигналов в обоих направлениях. Внутри метода get мы ждем (вызов wait), пока Pr

boolean valueSet = false;

synchronized int get() <

synchronized void put(int n) <

try wait(); catch(InterruptedException e);

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

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

Сводка функций программного интерфейса легковесных процессов

Ниже приведена сводка всех методов класса Thread, обсуждавшихся в этой главе.

Методы класса — это статические методы, которые можно вызывать непосредственно с именем класса Thread.

Статический метод currentThread возвращает объект Thread, выполняющийся в данный момент.

Вызов метода yield приводит к тому, что исполняющая система переключает контекст с текущего на следующий доступный подпроцесс. Это один из способов гарантировать, что низкоприоритетные подп

При вызове метода sleep исполняющая система блокирует текущий подпроцесс на n миллисекунд. После того, как этот интервал времени закончится, подпроцесс снова будет способен выполняться. В б

Метод start говорит исполняющей системе Java, что необходимо создать системный контекст подпроцесса и запустить этот подпроцесс. После вызова этого метода в новом контексте будет вызван мет

Метод run — это тело выполняющегося подпроцесса. Это — единственный метод интерфейса Runnable. Он вызывается из метода start после того, как исполняющая среда выполнит необходимые операции

Вызов метода stop приводит к немедленной остановке подпроцесса. Это — способ мгновенно прекратить выполнение текущего подпроцесса, особенно если метод выполняется в текущем подпроцессе. В т

Метод suspend отличается от метода stop тем, что метод приостанавливает выполнение подпроцесса, не разрушая при этом его системный контекст. Если выполнение подпроцесса приостановлено вызов

Метод resume используется для активизации подпроцесса, приостановленного вызовом suspend. При этом не гарантируется, что после вызова resume подпроцесс немедленно начнет выполняться, поскол

Метод setPriority устанавливает приоритет подпроцесса, задаваемый целым значением передаваемого методу параметра. В классе Thread есть несколько предопределенных приоритетов-констант: MIN_P

Этот метод возвращает текущий приоритет подпроцесса — целое значение в диапазоне от 1 до 10.

Метод setName присваивает подпроцессу указанное в параметре имя. Это помогает при отладке программ с параллельными подпроцессами. Присвоенное с помощью setName имя будет появляться во всех

Метод getName возвращает строку с именем подпроцесса, установленным с помощью вызова setName.

Есть еще множество функций и несколько классов, например, ThreadGroup и SecurityManager, которые имеют отношение к подпроцессам, но эти области в Java проработаны еще не до конца. Скажем лишь,

А дорога дальше вьется

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

Илон Маск рекомендует:  Конвертация статичного сайта в Jekyll
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL