Rob Pike, 2012
Продолжение, начало в части 1, части 2, части 3.
12. Конкурентность
Конкурентность важна для современной вычислительной среды с ее многоядерными машинами, на которых работают веб-серверы с несколькими клиентами, что можно назвать типичной программой Google. Этот вид программного обеспечения не особенно хорошо обслуживается C++ или Java, которым не хватает достаточной поддержки конкурентности на уровне языка.
Go воплощает в себе вариант CSP с каналами первого класса. CSP был выбран отчасти из-за фамильярности (один из нас работал над языками-предшественниками, основанными на идеях CSP), а также потому, что у CSP есть свойство, которое легко добавить в модель процедурного программирования без глубоких изменений в этой модели. То есть, учитывая C-подобный язык, CSP может быть добавлен к языку в основном ортогональным способом, обеспечивая дополнительную выразительную силу, не ограничивая другие применения языка. Кратко, остальная часть языка может оставаться "обычной".
Подход, таким образом, представляет собой композицию независимо выполняемых функций в противном случае регулярного процедурного кода.
Получившийся язык позволяет нам плавно соединить конкурентность с вычислениями. Рассмотрим веб-сервер, который должен проверять сертификаты безопасности для каждого входящего вызова клиента; в Go легко сконструировать программное обеспечение, используя CSP для управления клиентами как независимо выполняемыми процедурами, но при этом использовать все возможности эффективного компилируемого языка, доступного для дорогостоящих криптографических вычислений.
Таким образом, CSP является практичным для Go и для Google. При написании веб-сервера, канонической программы Go, модель отлично подходит.
Есть одно важное предостережение: Go не является полностью безопасным для памяти при наличии конкурентности. Совместное использование разрешено, а указатель на канал является идиоматическим (и эффективным).
Некоторые эксперты по конкурентности и функциональному программированию разочарованы тем, что Go не применяет подход с однократной записью к семантике значений в контексте конкурентных вычислений, что Go не похож на Erlang, например. Опять же, причина в значительной степени в знакомстве и пригодности для проблемной области. Конкурентные функции Go хорошо работают в контексте, знакомом большинству программистов. Go допускает простое, безопасное конкурентное программирование, но не запрещает плохое программирование. По соглашению мы компенсируем это обучением программистов думать о передаче сообщений как о версии контроля над владением. Девиз: "Не общайтесь, разделяя память, делитесь памятью, общаясь".
Наш ограниченный опыт работы с программистами, плохо знакомыми как с Go, так и с конкурентным программированием, показывает, что это практический подход. Программистам нравится простота, которую поддержка конкурентности приносит сетевому программному обеспечению, а простота порождает надежность.
13. Сборка мусора
Для языка систем, сборка мусора может быть спорной особенностью, но мы потратили очень мало времени, решив, что Go будет языком со сборщиком мусора. В Go нет явной операции освобождения памяти: единственный способ, которым выделенная память возвращается в пул, - через сборщик мусора.
Это было простое решение, потому что управление памятью сильно влияет на то, как язык работает на практике. В C и C++ слишком много программистских усилий тратится на выделение и освобождение памяти. Получающиеся проекты имеют тенденцию раскрывать детали управления памятью, которые вполне могут быть скрыты; наоборот, соображения памяти ограничивают то, как их можно использовать. В отличие от этого, сборка мусора упрощает определение интерфейсов.
Более того, в конкурентном объектно-ориентированном языке практически необходимо иметь автоматическое управление памятью, поскольку владение частью памяти может быть сложным в управлении, так как она передается между конкурентными выполнениями. Важно отделить поведение от управления ресурсами.
Язык намного проще в использовании из-за сборки мусора.
Конечно, сборка мусора сопряжена со значительными затратами: общие накладные расходы, задержка и сложность реализации. Тем не менее, мы считаем, что преимущества, которые в основном ощущаются программистом, перевешивают затраты, которые в основном несет разработчик языка.
Опыт работы с Java, в частности с языком сервера, заставил некоторых людей нервничать по поводу сбора мусора в пользовательской системе. Накладные расходы неконтролируемы, задержки могут быть большими, и для хорошей производительности требуется большая настройка параметров. Go, однако, другой. Свойства языка смягчают некоторые из этих проблем. Не все из них, конечно, но некоторые.
Ключевым моментом является то, что Go предоставляет программисту инструменты для ограничения выделения путем управления макетом структур данных. Рассмотрим определение простого типа структуры данных, содержащей буфер (массив) байтов:
type X struct {
a, b, c int
buf [256]byte
}
В Java поле buf потребует второго выделения и доступа к нему второго уровня косвенности. В Go, однако, буфер выделяется в одном блоке памяти вместе с содержащейся структурой, и косвенное обращение не требуется. Для системного программирования эта конструкция может иметь лучшую производительность, а также уменьшать количество элементов, известных сборщику. В масштабе это может иметь существенное значение.
Как более прямой пример, в Go легко и эффективно предоставить распределители второго порядка, например распределитель арены, который выделяет большой массив структур и связывает их вместе со свободным списком. Библиотеки, которые многократно используют множество таких небольших структур, могут при скромной предварительной подготовке не создавать мусора, но при этом быть эффективными и отзывчивыми.
Go - это язык со сборщиком мусора, опытный программист может ограничить нагрузку на сборщик и тем самым повысить производительность. (Кроме того, установка Go поставляется с хорошими инструментами для изучения производительности динамической памяти работающей программы.)
Чтобы дать программисту такую гибкость, Go должен поддерживать то, что мы называем внутренними указателями на объекты, расположенные в куче. Поле X.buf в приведенном выше примере находится внутри структуры, но допустимо захватить адрес этого внутреннего поля, например, чтобы передать его в процедуру ввода-вывода. В Java, как и во многих языках со сборщиком мусора, невозможно создать внутренний указатель, подобный этому, но в Go это идиоматично. Эта точка проектирования влияет на то, какие алгоритмы сбора данных можно использовать, и может усложнить их, но после тщательного обдумывания мы решили, что необходимо разрешить внутренние указатели из-за преимуществ для программиста и способности снизить нагрузку (возможно, сложнее реализовать) на сборщик. Пока что наш опыт сравнения похожих программ на Go и Java показывает, что использование внутренних указателей может оказать существенное влияние на общий размер арены, задержку и время сбора.
Таким образом, Go является языком со сборщиком мусора, но дает программисту некоторые инструменты для управления издержками сбора.
Сборщик мусора остается активной областью развития. Текущий дизайн представляет собой параллельный сборщик меток и разверток, и остаются возможности улучшить его производительность или, возможно, даже дизайн. (Спецификация языка не требует какой-либо конкретной реализации коллектора.) Тем не менее, если программист позаботится об разумном использовании памяти, текущая реализация хорошо работает для производственного использования.
Читайте также:
- Golang в Google: дизайн языка на службе разработки программ, часть 5
- Golang в Google: дизайн языка на службе разработки программ, часть 1
- Golang в Google: дизайн языка на службе разработки программ, часть 2
- Golang в Google: дизайн языка на службе разработки программ, часть 3
Комментариев нет:
Отправить комментарий