Pull to refresh

Comments 29

Гоферы всех стран, объединяйтесь! Кстати, confreaks буквально только что закончили выкладывать видео с недавней конференции по Go (GopherCon 2014).
Список видео по ссылке confreaks.com/events/gophercon2014.
Эх, вот бы кто сабы (пусть даже на инглише) сделал. Я бы поддержал такое начинание хотя бы тем же рублем.
А уже есть какие-нибудь хорошие книги по Golang?
А почему бы и нет… Книжники просыпаются где-то за неделю до конференции, хотя мы проводим в теже даты каждый год :-)
Такими темпами — отдельный поток Go — родиться на след. год. :-)
В процессе написания книга Go in Action от Manning. Купить можно сейчас по программе MEAP (получаете новые главы как только они выходят). Обычно в своём твиттере они публикуют дисконтные коды на 40-50%.
Если вдруг кто будет на конфе, спросите плз у Дмитрия, когда в Go можно будет работать напрямую с полями структур в мапе,
например someMap[idx].cnt++
Go реализован так, что когда вы используете индексное выражение как rvalue, оно возвращает копию значения из map, а не ссылку на него. Изменение копии смысла не имеет, поэтому оно и запрещено языком.

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

Так что это не баг.
Спасибо, но я в курсе источника этой проблемы.

На самом деле с любой мапой вообще нельзя работать паралельно из разных горутин, нужно в таких случаях использовать мютекс. Поэтому запрет на прямой доступ к полям структуры в мапе — это всего лишь особенность текущей реализации компилятора. Технически, компилятор все равно сначала вычисляет адрес данных в мапе, чтобы скопировать их, поэтому можно использовать этот адрес и для прямого доступа к полю стуктуры. Случай, когда пытается записаться поле для ключа, который отсутствует в мапе, тоже решается использованием значения по умолчанию для новосозданной структуры.
Ну технически да — можно возвращать прямо ссылку на сами данные в map. Но тогда программисту будет позволено взять указатель на элемент (&m[1]), сохранить его где-нибудь, а потом использовать, и получить трудноуловимые ошибки. Или то же самое, но неявно, если вызвать какой-нибудь метод у структуры, который принимает указатель:
func (av *a) f() {… }
И то же самое. Например, если f() выполняется долго / породит горутину, которая позже попытается обратиться к av, когда структура поменяется, тоже будет сбой. Go всё-таки спроектирован, чтобы как можно меньше давать стрелять в ногу.

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

Кстати, а почему не хотите сохранять в map указатели на структуры? Жёсткая оптимизация по памяти?
Речь не идет о том, чтобы разрешить брать ссылки на елементы мапы, такое делать разумеется нельзя. Адрес, который упоминался выше, это технический адрес для работы с данными (мы ж не про ассоциативную память говорим, которая может работать и без адреса), он доступен только компилятору. Программист этот адрес никогда не увидит.

Хранить в мапе указатели — это не только дополнительная нагрузка на проц из-за добавочного уровня адресации, но и дополнительная работа сборщику мусора (а для больших мап — очень сильно значительная).

Обновлять поле структуры через полную перезапись всей структуры — опять же огромный кусок лишней работы, который может повлечь и обновление внутренних данных мапы, хотя в этом никакой необходимости нет.
Да, я понимаю. Технический адрес — даже если он неявно доступен программисту, всё равно доступен: программист сможет менять содержимое структуры, и это может породить серьёзные ошибки. Может и не породить, если программист чётко знает, что никакая другая горутина в это время со структурой не работает. Как я писал, в виде исключения, можно разрешить простые операции типа m[1].b = 5, но запрещать потенциально сложные m[1].f(). Но делать такое различие в синтаксисе выглядит нелогичным.

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

То, что надо перезаписывать структуру при каждом обновлении, это факт — медленно. Лучше хранить указатели на структуры, и тогда можно спокойно делать m[1].b++.
Я уже упоминал, что нельзя работать с общей мапой из разных горутин без мютекса, из-за риска повредить внутренние данные. Соотв никаких ошибок работа с полем структуры напрямую породить не может, даже при работе с методами, а не просто полями.

Сборщик мусора блокирует выполнение программы на некоторое время. Пока мапа небольшая, ок, использовать ссылки терпимо, но на мапе в 1М ключей это уже проблемы.
Да, никаких ошибок, за исключением того, что метод может сохранить ссылку на объект, и ссылка будет где-то потом использована, уже будучи невалидной. Я отдаю себе отчёт в искусственности моделируемой ситуации, и что аккуратный разработчик сам проследит за судьбой всех указателей на кишки map. Но, тем не менее, это ещё один возможный вектор ошибок для всех остальных. Как мне кажется, это вполне сознательное решение разработчиков — не позволять изменять содержимое значений в map in-place.

По поводу сборщика мусора. Он у вас часто срабатывает? Если много мусора не производить, то и GC случается очень редко. Это как раз вполне контролируемо путём минимизации числа созданий и копирований объектов.
Можно подумать, что теперешняя необходимость контролировать мютекс, оперировать указателями и контролировать их инициализированность либо выгружать/перезаписывать структуру в мапе ведет к сильной простоте и надежности ))

Это решение скорее продиктовано скорее желанием сохранить максимальную простоту имплементации компилятора. Но принцип «и так сойдет» далеко не заведет. Go восхитительный язык, но все ещё требующий развития.

Pauseless GC позволит решать те задачи, которые сейчас не подходят для Go, Тем более что он и позиционируется больше для серверных задач, а там latency бывает критично.
Несмотря на то, что мьютексы в Go иногда бывают уместны, во многих случаях гораздо изящнее решить проблему доступа к общей структуре данных через отдельную горутину, которая «владеет» этой структурой, и принимает сообщения через канал от всех желающих эту структуру потрогать. Например, если вы делаете счётчик событий (как привели пример в начале этого треда), то у вас будет работать горутина типа такой:
for id := range ch {
m[id].b++
}
А когда кто-то хочет инкрементировать счётчик, то просто посылает запрос в канал:
ch < — 2
Подход хорошо описан тут: blog.golang.org/share-memory-by-communicating

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

Для контроля latency, чтобы такие «менеджеры данных» не стали узким местом (особенно, когда они делают какие-то тяжёлые и нетривиальные операции), отлично помогают буферизованные каналы:
ch := make(chan int, 100)

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

Про Pauseless GC согласен, было бы здорово :)
Работа с каналами внутри тоже использует мютексы, причем чаще, т.е. это уже больший оверхед. lockless записей в канал не бывает, т.к. иначе возможно повреждение внутренних данных или другие проблемы. Плюс оверхед на шедулинг горутин, оверхед на копирование данных в/из канала — это все не бесплатно. Код тоже может стать излишне сложным по сравнению с кодом на мютексах.

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

Каналы и мютексы это существенно разные инструменты, каждый подходит под свои задачи.
docs.google.com/a/google.com/document/d/1yIAYmbvL3JxOKOjuCyon7JhW4cSv1wy5hC0ApeGMV9s/pub
Тут описано, как работает блокирующая и неблокирующая запись в канал. Сначала отправитель пытается отправить данные неблокирующим образом. И если это получается, и в канале есть ресивер, то этот ресивер немедленно начинает исполняться. Поэтому, если у вас contention случается не часто, то приведённый мной выше пример будет практически бесплатен.

По поводу RLock/RUnlock согласен — там мьютексы подошли бы лучше.
А, посмотрел внимательнее — неправ немного. Просто отправка данных в канал lockless, а если надо разбудить горутину, то всё-таки есть блокировка. В любом случае, сами каналы не проигрывают явному использованию мьютексов по производительности, а ясности коду добавляют очень хорошо.
Отличная работа, Дмитрию респект. Ещё бы мапы улучшить теперь… )
Спасибо за новость! Давно хотел его ещё пощупать ещё со дня выхода.
Скажите пожалуйста а как сейчас дела у го со скоростью? Обогнал джаву? А то крайне давно видел тесты где джава была в разы быстрее.
Классно если бы показали создание веб приложения на го в связке с ангулар. Круто было бы
Sign up to leave a comment.