Как сделать переменную потокобезопасной - PullRequest
0 голосов
/ 17 октября 2018

Я относительно новичок в Go, и мне нужно сделать переменную потокобезопасной.Я знаю, что в Java вы можете просто использовать ключевое слово synchronized, но в go нет ничего подобного.Есть ли способ синхронизации переменных?

Ответы [ 2 ]

0 голосов
/ 18 октября 2018

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) могут подписываться на получение сообщений / обновлений, а брокер способен транслировать сообщения подписанным клиентам.В системе, где есть множество клиентов, которые могут подписаться / отписаться в любое время, и может возникнуть необходимость в рассылке сообщений в любое время, безопасная синхронизация всего этого будет сложной.Мудро используя каналы, эта реализация брокера довольно проста и понятна.Пожалуйста, позвольте мне не повторять код, но вы можете проверить его в этом ответе: Как транслировать сообщение по каналу .Реализация совершенно безопасна для одновременного использования, поддерживает «неограниченное количество» клиентов и не использует один мьютекс или общую переменную, только каналы.

Также см. Связанные вопросы:

Чтение значений из другого потока

0 голосов
/ 18 октября 2018

Есть ли способ синхронизации переменных?

Нет.

...