Comments 13
package counter
type Counter struct {
count int
}
func (c *Counter) Increment(n int) int {
c.count += n
return c.count
}
Но и это починить не сильно сложно, просто добавь
package counter
import (
"sync"
"sync/atomic"
)
type Counter struct {
count int64
mx sync.Mutex
}
func (c *Counter) Increment(n int64) int64 {
c.mx.Lock()
defer c.mx.Unlock()
c.count += n
return c.count
}
func (c *Counter) IncrementAtomic(n int64) int64 {
atomic.AddInt64(&c.count, n)
return atomic.LoadInt64(&c.count)
}
Чейни — мужик своеобразный, не всегда просто понять что он имеет в виду.
Вы можете изолированно тестировать её? Можете тестировать параллельно? Можете использовать одновременно несколько экземпляров?
можем ответить «Да», просто такое вполне может быть и в реальном проекте.
Например, далеко ходить не нужно, возьмём стандартную библиотеку net/http, и стандартный роутер для http сервера http.ServeMux.
Сделаем простой сервер (обработку ошибок опустим)
package main
import (
"net/http"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
})
go http.ListenAndServe(":3000", nil)
go http.ListenAndServe(":3001", nil)
select {}
}
В примере выше, зайдя на http://localhost:3000/hello и на http://localhost:3001/hello мы увидим одно и то же, так как http.HandleFunc по факту вызывает метод у глобальной переменной, а http.ListenAndServe при передаче вторым параметром nil её использует.
Хорошо ли это? Для прототипов, возможно, но это неявная «магия» и на вопросы выше уже не получится так однозначно ответить.
Но если мы сделаем тот же сервер с созданием нового роутера и передадим его явно, проблемы тут же исчезают:
package main
import (
"net/http"
)
func main() {
r1 := http.NewServeMux()
r1.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world 3000"))
})
r2:= http.NewServeMux()
r2.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world 3001"))
})
go http.ListenAndServe(":3000", r1)
go http.ListenAndServe(":3001", r2)
select {}
}
И такое не редко встречается, но данный пример не так опасен, так как в данном случае мы смогли создать новый экземпляр который не повлияет на остальные, но ведь существуют библиотеки, которые хочется выполнять в разных горутинах или параллельно, но не получается, так как у неё объявлена важная глобальная переменная, которая влияет на все остальные вызовы этой библиотеки.
func (c *Counter) IncrementAtomic(n int64) int64 {
return atomic.AddInt64(&c.count, n)
}
atomic.AddInt64
AddInt64 atomically adds delta to *addr and returns the new value.
Простое не значит лёгкое, это мы знаем..
А мне кажется, что в статье понятия простоты и лёгкости как раз перепутаны.
Go — лёгкий. Лёгкий потому что у него низкий порог входа, мало фич, и программы на нём писать «легко». Легко написанная программа на Go — простой от этого не становится.
Время освоения инструмента, это как константа в big-o нотации — значение имеет только на очень малом количестве входных данных.
При написании сложного проекта — важно сдерживать нелинейное возрастание сложности при росте проекта, а не пытаться уменьшать константу, и «сложные»(complex) инструменты могут быть проще в использовании, когда умешь ими пользоваться, для того они, собственно, и нужны.
Также хочется упомянуть тут этот прекрасный топик — habr.com/ru/post/469441, который показывает как решение в которое чуть сложнее вникнуть будет сильно проще.
И прекрасный доклад от Рича Хикки — Simplicity Matters.
Многие сравнивают эту лёгкость с примитивностью. С одной стороны это такой засахаренный си с малым количеством абстракций, но с другой стороны си далеко не примитивный, а очень даже мощный в том что позволяет делать. А го и не туда, и не сюда. Вроде и на питон чем то похож, но не такой гибкий и однородный, и вроде на си похож, но не такой могущественный. По сути его киллер-фича в компилируемости, и всё.
Разберем по частям.
засахаренный си
От си часть и убрали (всякие define, к указателю число нельзя прибавить), то что много боли несло как раз. Потом чуть добавили для нормальной структуры (модули, встраивание для ооп, конечно — GC, который очень хорош, и легковесные потоки, на которых можно делать асинхронный код) — это все полезные вещи, и их присутствие понравилось людям, язык развивается, проекты, фреймворки.
на питон чем то похож
с СИ, на мой взгляд, более корректное сравнение, потому что golang=си-трудныеМоменты+фичи. А с пайтоном он только си-подобным синтаксисом похож, и что многие задачи можно сделать на обоих языках
киллер-фича в компилируемости
киллер-фича в простоте и горутинах, вокруг этого выросла эко система, которая позволяет решать задачи, связанные с высокой нагрузкой.
К сожалению, качество перевода сильно хромает. Многие вещи явно не стоило переводить вообще, потому что приходится переводить в уме обратно просто чтобы понять, о чём речь. Тот случай, когда сильно пожалел, что просто не пошёл сразу читать в оригинале. :(
На всякий случай приведу нормальный перевод The Zen of Python:
Красивое лучше, чем уродливое.
Явное лучше, чем неявное.
Простое лучше, чем сложное.
Сложное лучше, чем запутанное.
Плоское лучше, чем вложенное.
Разреженное лучше, чем плотное.
Читаемость имеет значение.
Особые случаи не настолько особые, чтобы нарушать правила.
При этом практичность важнее безупречности.
Ошибки никогда не должны замалчиваться.
Если не замалчиваются явно.
Встретив двусмысленность, отбрось искушение угадать.
Должен существовать один — и, желательно, только один — очевидный способ сделать это.
Хотя он поначалу может быть и не очевиден, если вы не голландец.
Сейчас лучше, чем никогда.
Хотя никогда зачастую лучше, чем прямо сейчас.
Если реализацию сложно объяснить — идея плоха.
Если реализацию легко объяснить — идея, возможно, хороша.
Пространства имён — отличная вещь! Давайте будем делать их больше!
Дзен Go