Разница между атомарными операциями и блокировками мьютексов в языке Go

Go

Эта серия статей знакомит со многими методами, которые часто используются в параллельном программировании, за исключениемContext, таймеры, мьютексы, и есть техника вне канала --атомарная операцияИспользуется в некоторых алгоритмах синхронизации. В сегодняшней статье мы кратко разберемсяGoПоддержка атомарных операций в языке, а затем обсуждение разницы между атомарными операциями и мьютексами.

Основные темы статьи следующие:

  • атомарная операция
  • GoПоддержка атомарных операций
  • Разница между атомарной операцией и мьютексом

атомарная операция

Атомарная операция — это операция, которая не может быть прервана во время процесса.Во время выполнения атомарной операции над определенным значением ЦП никогда не будет выполнять другие операции над значением. Чтобы достичь такого уровня строгости, атомарные операции представляются и выполняются только одной инструкцией ЦП. Атомарные операции не требуют блокировки и часто реализуются непосредственно с помощью инструкций ЦП. На самом деле реализации других методов синхронизации часто полагаются на атомарные операции.

Перейти на поддержку атомарных операций

Перейти на языкsync/atomicПакет обеспечивает поддержку атомарных операций для синхронизации доступа к целым числам и указателям.

  • Атомарные операции, предоставляемые языком Go, не являются навязчивыми.
  • Эти функции обеспечивают пять атомарных операций: сложение и вычитание, сравнение и обмен, загрузка, сохранение и обмен.
  • Типы, поддерживаемые атомарными операциями, включают int32, int64, uint32, uint64, uintptr, unsafe.Pointer.

В следующем примере показано, как использоватьAddInt32Функция выполняет атомарную операцию добавления над значением int32. В этом примереmain goroutineСоздано 1000 одновременныхgoroutine. каждый вновь созданныйgoroutineУвеличьте целое число n на 1.

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
    var n int32
	  var wg sync.WaitGroup
	  for i := 0; i < 1000; i++ {
		    wg.Add(1)
		    go func() {
			      atomic.AddInt32(&n, 1)
			      wg.Done()
		    }()
	  }
	  wg.Wait()

    fmt.Println(atomic.LoadInt32(&n)) // output:1000
}

В приведенном выше примере вы можете проверить это самостоятельно, если мы не используемatomic.AddInt32(&n, 1)Вместо этого просто соедините переменнуюnРезультат, полученный автоинкрементом, не тот 1000, который мы ожидали, что мы и получили в статье "Гонки данных и решения в параллельном программировании на Go», упомянутых в проблеме гонки данных, атомарные операции могут обеспечить этиgoroutineМежду ними нет гонки данных.

Сравните и поменяйте местами атомарные операции для краткостиCAS(Сравнить и поменять местами), вsync/atomicpackage, такие атомарные операции представлены именами, начинающимися сCompareAndSwapПредоставляет несколько функций для префикса

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer,old, new unsafe.Pointer) (swapped bool)
......

После вызова функцииCompareAndSwapФункция сначала определит параметрыaddrЗначение операции и параметры, на которые указываютoldравны ли значения, только если результат этого сужденияtrueПосле этого будут использоваться параметрыnewПредставленное новое значение заменяет старое значение, в противном случае операция игнорируется.

мы используемmutexМьютексы похожи на пессимистические блокировки.Всегда предполагается, что будут параллельные операции по изменению обрабатываемого значения, поэтому блокировки используются для размещения связанных операций в критических секциях для защиты. при использованииCASОперации имеют тенденцию к оптимистичной блокировке, всегда предполагая, что обрабатываемое значение не изменилось (т. е. равно старому значению), и выполняя замену значения, как только подтверждается правильность этого предположения. Когда управляемое значение часто изменяется,CASОперацию не так просто выполнить, поэтому вам нужно продолжать попытки, пока не добьетесь успеха.

package main

import (
    "fmt"
    "sync/atomic"
)

var value int32 = 1

func main()  {
    fmt.Println("======old value=======")
    fmt.Println(value)
    addValue(10)
    fmt.Println("======New value=======")
    fmt.Println(value)

}

//不断地尝试原子地更新value的值,直到操作成功为止
func addValue(delta int32){
    for {
        v := value
        if atomic.CompareAndSwapInt32(&value, v, (v + delta)){
            break
        }
    }
}

В приведенном выше случае сравнения и обменаv:= valueкак переменнаяvзадание, но имейте в виду, что при чтенииvalueВо время операции другие операции чтения и записи для этого значения могут выполняться одновременно, поэтому эта операция чтения, скорее всего, будет считывать данные, которые были изменены только наполовину. Итак, мы собираемся использоватьsync/atomicПакет кода предоставляет намLoadпрефиксные функции, чтобы избежать таких плохих вещей.

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

оatomicДля более подробного ознакомления с использованием пакета вы можете посетить официальныйсинхронизация/атомарная китайская документация

Разница между атомарной операцией и мьютексом

Мьютекс — это структура данных, позволяющая выполнять ряд взаимоисключающих операций. В то время как атомарная операция — это единственная операция, которая является взаимоисключающей, что означает, что никакой другой поток не может ее прервать. тогдаGoна языкеatomicАтомарные операции в пакетах иsyncВ чем разница между блокировками синхронизации, предоставляемыми пакетом?

во-первыхatomicПреимущество операции в том, что она легче, напримерCASОперации замены значений с защитой от параллелизма можно выполнять без формирования критических секций и создания мьютексов. Это может значительно уменьшить потерю синхронизации при работе программы.

Атомарные операции также имеют недостатки. или сCASВ качестве примера используйте операциюCASПрактика эксплуатации склонна к оптимизму, всегда предполагая, что оперируемое значение не было изменено (то есть равно старому значению), и как только подтверждается подлинность этого предположения, значение немедленно заменяется, а затем в случай, когда действующее значение часто изменяется,CASОперация не так-то просто увенчалась успехом. Практика использования мьютекса имеет тенденцию быть пессимистичной: мы всегда предполагаем, что будут параллельные операции для изменения значения операции, и используем блокировки для размещения связанных операций в критических секциях для защиты.

Итак, если подытожить разницу между атомарными операциями и блокировками мьютексов, то:

  • Мьютекс — это структура данных, позволяющая потоку выполнять критическую часть программы для выполнения нескольких взаимоисключающих операций.
  • Атомарная операция — это единственная взаимоисключающая операция над значением.
  • Блокировки взаимного исключения можно понимать как пессимистические блокировки: общие ресурсы используются только одним потоком за раз, другие потоки блокируются, а ресурсы передаются другим потокам после их использования.

atomicПакет предоставляет низкоуровневые примитивы атомарной памяти, полезные для реализации алгоритмов синхронизации. Эти функции нужно использовать очень осторожно. Неправильное использование увеличит накладные расходы системных ресурсов. Для прикладного уровня лучше использовать каналы илиsyncФункции, предусмотренные в пакете, для завершения операции синхронизации.

противatomicТочка зрения сумки также много обсуждается в группе рассылки Google, и один из выводов объясняется:

Этот пакет следует избегать. Кроме того, прочитайте главу «Атомарные операции» стандарта C++11; если вы понимаете, как безопасно использовать эти операции в C++, то вы можете безопасно использовать Gosync/atomicвозможность упаковки.

Рекомендуемое чтение

Идеи решения проблем и планировщик языка Go для параллельных задач