Разработка сред управляемого исполнения на примере виртуальной машины Java Занятие 4

advertisement
Разработка сред управляемого
исполнения на примере
виртуальной машины Java
Занятие 4
Салищев С.И.
Нити и Планировщик
Планировщик
Нить
приоритет
код
данные
Процессор
Создание и разрушение нитей
Распределение нагрузки между процессорами
Приостановка и возобновление нитей



Для синхронизации управляемого кода
Для сборки мусора
Для синхронизации неуправляемого кода и сборки мусора
Синхронизация в Java
synchronized отображается в Monitor lock/unlock
Методы Object отображаются в операции монитора
Монитор





lock – захват монитора, ожидание в случае конфликта
unlock – освобождение захваченного монитора
wait – освобождение монитора и ожидание извещения. Только для
захваченного монитора
notify – извещение одной ожидающей нити. Только для
захваченного монитора
notifyAll – извещение всех ожидающих нитей. Только для
захваченного монитора
При ожидании планировщик не планирует исполнение кода нити
При извещении и освобождении монитора нить снова может
быть запланирована для исполнения
Каузальная модель памяти Java
Физически результаты исполнения операций могут
переупорядочиваться




Одновременным исполнением на разных процессорах
Суперскалярным процессором
Кешами памяти
Оптимизирующим компилятором
Важен только порядок регистрации наблюдателем (см.
логические часы Лампорта)
На одной нити все операции с памятью выполняются в порядке
исполнения.
Для различных нитей все операции с памятью (кроме long и
double) атомарные. ПОРЯДОК НЕ ОПРЕДЕЛЕН!
До Java 1.5 упорядочивались только операции синхронизации
С Java 1.5 каузальная модель памяти (JLS 3.0, 17.4)
Синхронизованные операции





Volatile read/write
Monitor lock/unlock
Синтетические первая и последняя операции нити
Запуск исполнения и проверка завершения нити
Прерывание нити и обнаружение прерывания нити
Существует строгий порядок всех
синхронизованных операций в программе

согласован с порядком операций каждой нити
Отношение синхронизации
(synchronizes with)
Отношение “синхронизации c”






M.unlock со всеми последующими M.lock
V.write со всеми последующими V.read
Запуск исполнения нити с первой операцией нити
Инициализация значения по умолчанию с первой
операцией всех нитей
Последняя операция нити с обнаружением
завершения нити (Thread.isAlive, Thread.Join)
Прерывание нити с обнаружением прерывания
нити (InterruptedException, Thread.interrupted,
Thread.isInterrupted)
Отношение предшествования
(happens before)
Пусть x, y – операции. Будем писать hb(x, y) для обозначения x
предшествует y.
hb – частичный порядок операций
hb(x, y) если





x, y исполняются на одной нити и x встречается перед y в
естественном порядке исполнения
x – последняя операция конструктора объекта, y – первая операция
Object.finalize() того же объекта
x синхронизована с y
hb(x, z) и hb(z, y) – транзитивность
запись x предшествует чтению у по семантике final поля (JLS 3.0,
17.5)
ВНИМАНИЕ! Если для z не определен порядок относительно x и
y, то во время исполнения z порядок исполнения x и у
неизвестен.
Пример (double check)
Неправильно
Правильно
class A {
R r;
R getR() {
if (r == null) {
synchronized {
if (r == null) {
r = new R();
}
}
}
return r;
}
}
class A {
volatile R r;
R getR() {
if (r == null) {
synchronized {
if (r == null){
r = new R();
}
}
}
return r;
}
}
Примеры упорядоченности
synchronized void m() {
notifyAll();
wait();
}
notifyAll не извещает
следующий wait даже в
том же блоке
class A {
final B b;
A() {
b = new B();
}
}
Чтение b возвращает
ссылку на полностью
инициализированный
объект
Атомарные операции
CAS(addr, old, new) (Atomic Compare and Swap)


За одну операцию сравнивает текущее значение со старым
и, если совпадает, меняет на новое
Если не совпадает, возвращает ошибку
Пример: Atomic Increment

while (!CAS(&x, x, x + 1));
Write Barrier – сохраняет изменения из кэша
процессора в память, ограничивает
переупорядочивание записей в будущее
Read Barrier – обновляет кэш процессора из памяти,
ограничивает переупорядочивание чтений в прошлое
Мониторы (fat locks)
Object
Header
Monitor
count
mutex
owner
wait
lock
Thread
Thread
Thread
Thread
Thread
Thread
Заголовок объекта содержит ссылку на структуру монитора либо
объект отображается при помощи хеш таблицы
Монитор





нить владельца
счетчик рекурсии
синхронизационный ресурс ОS
список нитей, ждущих освобождения монитора
список нитей, ждущих нотификации
Тонкие мониторы (thin locks)
Object Header
23
threadId
monitorRef
1
fat = 0
fat = 1
lock/unlock x
free
fat = 0
threadId = 0
count = 0
CAS
lock
unlock
uncontended
fat = 0
threadId = x
count = y
CAS
deflate
8
count
lock !x
inflate
contended
fat = 1
CAS
lock/unlock
unlock
При чтении используется Read Barrier
Сдувание монитора редко реализуется из-за сложности
Резервация мониторов
Object Header
1
1
22
fat = 0 unreserved threadId
fat = 1
monitorRef
8
count
lock/unlock x unreserve
free to reserve
reserved
unreserved = 0 CAS unreserved = 0
fat = 0
fat = 0
lock x
threadId = 0
threadId = x
count = 0
count = у
lock !x
count > 0
contended
fat = 1
uncontended
unreserved = 1
fat = 0
threadId = x
count = y
Операция снятия резервации требует остановки нити
владельца, является дорогостоящей
Операция снятия резервации может быть выполнена, если нить
владелец не запланирована для исполнения, например ожидает
в wait
Типы планирования
Вытесняющее планирование



Возможность остановки в любой инструкции
Согласовано с планировщиком многозадачных OS
Сложно согласовать с GC
Кооперативное планирование




Остановка в безопасных точках (Yield Point, Safe Point, GC
Point)
Предпочтительный способ при реализации в ядре OS
Требует ожидания перепланировки при остановке нити
планировщиком OS, может приводить к задержке
Хорошо согласуется с GC
Безопасные точки
Вызов метода
Создание объекта
Обратное ветвление
Синхронизация
Инструкции генерирующие исключение
(Potential Exception Instruction)
Взаимодействие с GC
Расположение ссылок на объекты на стеке и в
локальных переменных


Требуется при перечислении корневых ссылок
Компилятор может использовать регистры
Карты ссылок (GC Maps) генерируются компилятором


При вытесняющем планировании для каждой инструкции
процессора
При кооперативном планировании для каждой безопасной
точки
Правило Гослинга (Gosling property) – для любой
точки корректного Java метода карта ссылок не
зависит от пути в графе управления, приведшего в
эту точку
Системные нити
1:1 отображение управляемых и OS нитей
Простой планировщик через вызовы OS
Любой способ планирования

При кооперативном планировании высокая
вероятность конфликта с планировщиком OS
Требуется синхронизация данных между
всеми нитями
Структура стека определяется OS

Ограничение количества нитей
Зеленые (green, logical) нити
Простой вид M:N отображения нитей


М – управляемые нити, N – нити OS
N ≤ P – количество процессоров
Кооперативное планирование нитей



Планирование по времени может не использоваться. Упрощение
системы, но опасность голодания.
При реализации планирования по времени усложнение
планировщика
Уменьшается вероятность небезопасной остановки нитей
планировщиком OS.
Не требуется синхронизация данных между управляемыми
нитями ассоциированными с одним процессором
Невозможность использования процессора при системном
вызове
Любая структура стека

Количество нитей не ограничено OS
Комбинированные M:N нити
Сложное отображение нитей


М – управляемые нити, N – нити
операционной системы
N ≥ P – количество процессоров
При системном вызове создается
запасная нить ассоциированная с тем
же процессором
Усложнение синхронизации между
нитями OS на одном процессе
Download