synchronized
в Java - это средство, позволяющее только одному потоку выполнять блок кода (в любой момент времени).
В Go есть множество конструкций для достижения этого (например, мьютексы, каналы,группы ожидания, примитивы в sync/atomic
), но пословица Го: «Не связывайтесь, разделяя память; вместо этого делитесь памятью, общаясь».
Так что вместо этогоблокируя и разделяя переменную, попробуйте не делать этого, а вместо этого передайте результат между программами, например, используя каналы (чтобы вам не пришлось обращаться к общей памяти).Для получения дополнительной информации см. Блог Go: делитесь памятью с помощью связи .
Конечно, могут быть случаи, когда простейшим прямым решением является использование мьютекса для защиты одновременного доступа от нескольких программ кПеременная.В этом случае вы можете сделать это следующим образом:
var (
mu sync.Mutex
protectMe int
)
func getMe() int {
mu.Lock()
me := protectMe
mu.Unlock()
return me
}
func setMe(me int) {
mu.Lock()
protectMe = me
mu.Unlock()
}
Приведенное выше решение может быть улучшено в нескольких областях:
Использование sync.RWMutex
вместо sync.Mutex
, так что getMe()
может блокироваться только для чтения, поэтому несколько одновременных считывателей не будут блокировать друг друга.
После (успешной) блокировки желательно разблокировать с помощью defer
, поэтому, если в последующем коде произойдет что-то плохое (например, паника во время выполнения), мьютекс все равно будет разблокирован, избегая утечек ресурсов и взаимоблокировок.Хотя этот пример очень прост, ничего плохого не может произойти и не гарантирует безусловного использования отложенной разблокировки.
Рекомендуется держать мьютекс близко к данным, которые он должен защищать,Так что «обёртывание» protectMe
и его mux
в структуру - хорошая идея.И если у нас это получается, мы также можем использовать встраивание, поэтому блокировка / разблокировка становится более удобной (если только эта функциональность не должна быть открыта).Подробнее см. Когда вы встраиваете мьютекс в структуру в Go?
Так что улучшенная версия приведенного выше примера может выглядеть следующим образом (попробуйте на Go Playground ):
type Me struct {
sync.RWMutex
me int
}
func (m *Me) Get() int {
m.RLock()
m.RUnlock()
return m.me
}
func (m *Me) Set(me int) {
m.Lock()
m.me = me
m.Unlock()
}
var me = &Me{}
func main() {
me.Set(2)
fmt.Println(me.Get())
}
У этого решения есть еще одно преимущество: если вам потребуется несколько значений Me
, оно автоматически будет иметь разные отдельные мьютексы для каждого значения (наше первоначальное решение будеттребует создания отдельных мьютексов вручную для каждого нового значения).
Хотя этот пример верен и действителен, он может оказаться непрактичным.Потому что защита одного целого числа на самом деле не требует мьютекса.Мы могли бы добиться того же, используя пакет sync/atomic
:
var protectMe int32
func getMe() int32 {
return atomic.LoadInt32(&protectMe)
}
func setMe(me int32) {
atomic.StoreInt32(&protectMe, me)
}
Это решение короче, чище и быстрее.Если ваша цель - защитить только одно значение, это решение является предпочтительным.Если структура данных, которую вы должны защищать, более сложна, atomic
может быть даже нежизнеспособным, и использование мьютекса может быть оправдано.
Теперь, после демонстрации примеров совместного использования / защиты переменных, мы также должны датьпример того, чего мы должны стремиться достичь, чтобы достичь «Не связывайтесь, делясь памятью; вместо этого делитесь памятью, общаясь».
Ситуация такова, что у вас есть несколько одновременных программ ивы используете переменную, где вы храните некоторое состояние.Одна процедура изменяет (устанавливает) состояние, а другая читает (получает) состояние.Чтобы получить доступ к этому состоянию из нескольких программ, доступ должен быть синхронизирован.
И идея состоит в том, чтобы не иметь «разделяемой» переменной, подобной этой, а вместо этого состояние, которое установит одна программа, вместо этого она должна «отправить» ее, а другая программа, которая будет читать ее., он должен быть тем, в который «отправлено» состояние (или, другими словами, другая программа должна получить измененное состояние).Таким образом, нет общей переменной состояния, вместо этого существует связь между 2 программами.Go обеспечивает отличную поддержку для такого рода межгрупповых коммуникаций: каналов .Поддержка каналов встроена в язык, есть операторы отправки , операторы приема и другая поддержка (например, вы можете перебирать значения, отправленные на канал),Для вступления и подробностей, пожалуйста, проверьте этот ответ: Для чего используются каналы Голанга?
Давайте посмотрим на практический / реальный пример: «брокер».Брокер - это объект, в котором «клиенты» (goroutines) могут подписываться на получение сообщений / обновлений, а брокер способен транслировать сообщения подписанным клиентам.В системе, где есть множество клиентов, которые могут подписаться / отписаться в любое время, и может возникнуть необходимость в рассылке сообщений в любое время, безопасная синхронизация всего этого будет сложной.Мудро используя каналы, эта реализация брокера довольно проста и понятна.Пожалуйста, позвольте мне не повторять код, но вы можете проверить его в этом ответе: Как транслировать сообщение по каналу .Реализация совершенно безопасна для одновременного использования, поддерживает «неограниченное количество» клиентов и не использует один мьютекс или общую переменную, только каналы.
Также см. Связанные вопросы:
Чтение значений из другого потока