Чтение онлайн

на главную - закладки

Жанры

Кодеры за работой. Размышления о ремесле программиста

Сейбел Питер

Шрифт:

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

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

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

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

Я написал собственный диспетчер блокировок, поэтому стал подозревать его. Но ведь он без проблем прошел модульные тесты! Наконец я определил, что виноват был не он, а реализация мьютексов в нижележащем слое. Тогда операционные системы еще не поддерживали многопоточность, и пакет для ее поддержки нам пришлось писать самим. Вышло так, что разработчик, отвечавший за код мьютексов, случайно перепутал метки подпрограмм “заблокировать” и “попробовать заблокировать” в ассемблерной реализации потоков в Solaris. Так что каждый раз, когда вы думали, что вызываете безусловную блокировку, на самом деле она только пыталась произойти, и наоборот. И когда случался конфликт - в то время редкость, - второй поток оказывался в критической секции, как если бы в первом потоке не было блокировки. Самое забавное, что вся компания на несколько недель оказалась без мьютексов, и никто не заметил.

В своей превосходной статье “Engineering a Sort Function” (Разработка функции Sort) Бентли и Макилрой цитируют чудесное высказывание Кнута насчет приведения себя в самое поганое настроение, на которое только вы способны. Как раз это я и сделал для той серии тестов. Но это сделало ошибку крайне трудно обнаружимой. Прежде всего, из-за многопоточности каждый случай оказывался почти невоспроизводимым. Далее, оказались ложными мои представления не о чем-нибудь, а о ядре системы. Обычно начинающие программисты легко приходят к выводу, что язык или система не в порядке. Но тут базовая конструкция, на которую я опирался, - мьютекс - действительно оказалась сломанной.

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

Блох: Мне кажется, хорошая автоматическая программа проверки мьютексов спасла бы меня

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

Сейбел: Мы говорили о пошаговом прохождении кода. А какими средствами отладки вы пользуетесь сейчас?

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

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

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

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

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

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

Выглядит логично, так? Но, к сожалению, те, кто создавал объекты в стеке, делали их слишком большими по тогдашним меркам. Массив из 100 элементов, по 4 Кбайт каждый, - всего 400 Кбайт в стеке одного потока. Получался перескок через красную зону стека в стек соседнего потока. И мы получали неверный идентификатор потока. Хуже того: когда поток обращался к локальным для потока переменным, он считывал переменные другого потока, поскольку его идентификатор использовался как ключ для доступа к этим переменным.

Итак, то, что мы приняли за безобидный недочет трассировочного пакета, оказалось признаком действительно серьезной ошибки. Событие приписывалось потоку 43 вместо потока 42, так как один поток невольно подменял собой другой, и это могло иметь катастрофические последствия.

Вот почему нам нужны языки с хорошими параметрами безопасности. Лучше обойтись без таких случаев. Недавно у меня был разговор в одном университете: там хотели обучать программистов сначала языкам Си и C++, а потом Java, так как они хотели, чтобы программисты овладели системой “на всю глубину”. Меня спросили, что я думаю об этом.

Поделиться:
Популярные книги

Газлайтер. Том 18

Володин Григорий Григорьевич
18. История Телепата
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Газлайтер. Том 18

Секретарь лорда Демона

Лунёва Мария
Фантастика:
попаданцы
фэнтези
5.00
рейтинг книги
Секретарь лорда Демона

Жатва душ. Остров мертвых

Сугралинов Данияр
Фантастика:
боевая фантастика
рпг
5.20
рейтинг книги
Жатва душ. Остров мертвых

Кодекс Крови. Книга ХII

Борзых М.
12. РОС: Кодекс Крови
Фантастика:
боевая фантастика
попаданцы
5.00
рейтинг книги
Кодекс Крови. Книга ХII

Протокол "Наследник"

Лисина Александра
1. Гибрид
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Протокол Наследник

Найденыш

Шмаков Алексей Семенович
2. Светлая Тьма
Фантастика:
юмористическое фэнтези
городское фэнтези
аниме
5.00
рейтинг книги
Найденыш

Служанка. Второй шанс для дракона

Шёпот Светлана
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Служанка. Второй шанс для дракона

Кодекс Охотника. Книга XXI

Винокуров Юрий
21. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Кодекс Охотника. Книга XXI

Флеш Рояль

Тоцка Тала
Детективы:
триллеры
7.11
рейтинг книги
Флеш Рояль

Имперский Курьер. Том 2

Бо Вова
2. Запечатанный мир
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Имперский Курьер. Том 2

Громовая поступь. Трилогия

Мазуров Дмитрий
Громовая поступь
Фантастика:
фэнтези
рпг
4.50
рейтинг книги
Громовая поступь. Трилогия

Наследница долины Рейн

Арниева Юлия
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Наследница долины Рейн

Демон

Парсиев Дмитрий
2. История одного эволюционера
Фантастика:
рпг
постапокалипсис
5.00
рейтинг книги
Демон

Ищу жену с прицепом

Рам Янка
2. Спасатели
Любовные романы:
современные любовные романы
6.25
рейтинг книги
Ищу жену с прицепом