В параллельном программировании основная функция примитивов синхронизации, то есть того, что мы обычно называем блокировками, состоит в том, чтобы гарантировать, что несколько потоков илиgoroutine
Нет никакой путаницы при доступе к одному и тому же участку памяти.Go
лингвистическийsync
Пакет предоставляет общие примитивы синхронизации для параллельного программирования.Статья перепечатана в предыдущем выпуске "Примитивы синхронизации для параллельного программирования в Golangтакже подробно вMutex
,RWMutex
,WaitGroup
,Once
а такжеCond
Принцип реализации этих примитивов синхронизации. В сегодняшней статье давайте вернемся к прикладному уровню и сосредоточимся наsync
Также будут представлены сценарии применения этих примитивов синхронизации в пакете.sync
в упаковкеPool
а такжеMap
сценарии применения и методы использования. Без дальнейших церемоний, давайте начнем.
sync.Mutex
sync.Mutex
может бытьsync
Наиболее широко используемый примитив в пакете. Он разрешает взаимоисключающий доступ (не одновременно) к общим ресурсам:
mutex := &sync.Mutex{}
mutex.Lock()
// Update共享变量 (比如切片,结构体指针等)
mutex.Unlock()
Следует отметить, что после первого использованияsync.Mutex
сделать копию. (sync
Все примитивы пакета одинаковы). Если в структуре есть поле примитива синхронизации, оно должно передаваться по указателю.
sync.RWMutex
sync.RWMutex
это мьютекс для чтения и записи, который обеспечивает то, что мы только что видели вышеsync.Mutex
изLock
а такжеUnLock
метод (поскольку обе структуры реализуютsync.Locker
интерфейс). Однако он также позволяет использоватьRLock
а такжеRUnlock
метод для параллельного чтения:
mutex := &sync.RWMutex{}
mutex.Lock()
// Update 共享变量
mutex.Unlock()
mutex.RLock()
// Read 共享变量
mutex.RUnlock()
sync.RWMutex
разрешить существование по крайней мере одной блокировки чтения или одной блокировки записи, в то время какsync.Mutex
Разрешить существование блокировки чтения или записи.
Сравните производительность этих методов путем сравнительного анализа:
BenchmarkMutexLock-4 83497579 17.7 ns/op
BenchmarkRWMutexLock-4 35286374 44.3 ns/op
BenchmarkRWMutexRLock-4 89403342 15.3 ns/op
Видно заблокировано/разблокированоsync.RWMutex
Блокировки чтения быстрее, чем блокировка/разблокировкаsync.Mutex
быстрее, с другой стороны, вsync.RWMutex
звонитьLock()
/ Unlock()
самая медленная операция.
Поэтому его следует использовать только в сценариях с частым чтением и нечастой записью.sync.RWMutex
.
sync.WaitGroup
sync.WaitGroup
Это также часто используемый примитив синхронизации.goroutine
жду группуgoroutine
Исполнение завершено.
sync.WaitGroup
Имеет внутренний счетчик. когда счетчик равен0
время, тогдаWait()
Метод возвращается немедленно. В противном случае он заблокирует выполнениеWait()
методgoroutine
пока счетчик не сравняется0
время.
Чтобы увеличить счетчик, мы должны использоватьAdd(int)
метод. Чтобы уменьшить его, мы можем использоватьDone()
(уменьшить счетчик1
), вы также можете передавать отрицательные числа вAdd
метод уменьшает счетчик на указанный размер,Done()
Нижний слой метода сквозной.Add(-1)
осуществленный.
В следующем примере мы начнем с восьмиgoroutine
, и дождитесь их завершения:
wg := &sync.WaitGroup{}
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
// Do something
wg.Done()
}()
}
wg.Wait()
// 继续往下执行...
каждый раз, когда вы создаетеgoroutine
, мы будем использоватьwg.Add(1)
увеличиватьwg
внутренний счетчик. мы также можемfor
вызывается перед цикломwg.Add(8)
.
При этом каждыйgoroutine
Когда это сделано, оба используютwg.Done()
уменьшатьwg
внутренний счетчик.
main goroutine
будет через восемьgoroutine
оба исполняютwg.Done()
изменить счетчик на0
Затем вы можете продолжить выполнение.
sync.Map
sync.Map
это параллельная версияGo
лингвистическийmap
Мы можем:
- использовать
Store(interface {},interface {})
Добавьте элементы. - использовать
Load(interface {}) interface {}
Получить элементы. - использовать
Delete(interface {})
Удалить элементы. - использовать
LoadOrStore(interface {},interface {}) (interface {},bool)
Получить или добавить элементы, которых раньше не было. если бы ключ был раньшеmap
существует, возвращенное логическое значениеtrue
. - использовать
Range
Итерация по элементам.
m := &sync.Map{}
// 添加元素
m.Store(1, "one")
m.Store(2, "two")
// 获取元素1
value, contains := m.Load(1)
if contains {
fmt.Printf("%s\n", value.(string))
}
// 返回已存value,否则把指定的键值存储到map中
value, loaded := m.LoadOrStore(3, "three")
if !loaded {
fmt.Printf("%s\n", value.(string))
}
m.Delete(3)
// 迭代所有元素
m.Range(func(key, value interface{}) bool {
fmt.Printf("%d: %s\n", key.(int), value.(string))
return true
})
Вышеупомянутая программа выведет:
one
three
1: one
2: two
Как вы видете,Range
Метод получает типfunc(key,value interface {})bool
параметры функции. если функция возвращаетfalse
, итерация останавливается. Забавный факт, что даже если мы вернемся через постоянное времяfalse
, временная сложность в худшем случае по-прежнемуO(n)
.
когда мы должны использоватьsync.Map
вместо обычногоmap
использовать наsync.Mutex
?
- когда мы
map
Когда есть частые чтения и нечастые записи. - когда несколько
goroutine
При чтении, записи и перезаписи непересекающихся ключей. Что именно это означает? Например, если у нас есть реализация шардинга с набором из 4goroutine
, каждыйgoroutine
Отвечает за 25% ключей (каждый ответственный ключ не конфликтует). при этих обстоятельствах,sync.Map
является предпочтительным.
sync.Pool
sync.Pool
это параллельный пул, отвечающий за безопасное хранение набора объектов. Он имеет два метода экспорта:
-
Get() interface{}
Используется для выборки элементов из параллельного пула. -
Put(interface{})
Добавляет объект в параллельный пул.
pool := &sync.Pool{}
pool.Put(NewConnection(1))
pool.Put(NewConnection(2))
pool.Put(NewConnection(3))
connection := pool.Get().(*Connection)
fmt.Printf("%d\n", connection.id)
connection = pool.Get().(*Connection)
fmt.Printf("%d\n", connection.id)
connection = pool.Get().(*Connection)
fmt.Printf("%d\n", connection.id)
выход:
1
3
2
должен быть в курсеGet()
Метод будет случайным образом извлекать объекты из параллельного пула, и нет гарантии, что объекты, хранящиеся в параллельном пуле, будут получены в фиксированном порядке.
Может такжеsync.Pool
Укажите метод создателя:
pool := &sync.Pool{
New: func() interface{} {
return NewConnection()
},
}
connection := pool.Get().(*Connection)
Таким образом, каждый вызовGet()
, будет возвращенpool.New
Объект (в данном случае указатель), созданный функцией, указанной в .
Итак, когда использовать sync.Pool? Есть два варианта использования:
Во-первых, когда нам нужно повторно использовать общие и долгоживущие объекты (например, соединения с базой данных). Второй используется для оптимизации распределения памяти.
Рассмотрим пример функции, которая записывает буфер и сохраняет результат в файл в файле. использоватьsync.Pool
, мы можем повторно использовать пространство, выделенное для буфера, повторно используя один и тот же объект между различными вызовами функций.
Первым шагом является получение ранее выделенного буфера (если это первый вызов, создайте его, но это абстрактно). Потом,defer
Операция состоит в том, чтобы вернуть буфер обратноsync.Pool
середина.
func writeFile(pool *sync.Pool, filename string) error {
buf := pool.Get().(*bytes.Buffer)
defer pool.Put(buf)
// Reset 缓存区,不然会连接上次调用时保存在缓存区里的字符串foo
// 编程foofoo 以此类推
buf.Reset()
buf.WriteString("foo")
return ioutil.WriteFile(filename, buf.Bytes(), 0644)
}
sync.Once
sync.Once
— это простой, но мощный примитив, обеспечивающий выполнение функции только один раз. В приведенном ниже примере есть только одинgoroutine
Отображается выходное сообщение:
once := &sync.Once{}
for i := 0; i < 4; i++ {
i := i
go func() {
once.Do(func() {
fmt.Printf("first %d\n", i)
})
}()
}
мы использовалиDo(func ())
метод, чтобы указать раздел, который можно вызвать только один раз.
sync.Cond
sync.Cond
может бытьsync
Наименее распространенный из примитивов синхронизации, предоставляемых пакетом, он используется для передачи сигналов (один-к-одному) или широковещательной рассылки (один-ко-многим) наgoroutine
. Давайте рассмотрим сценарий, в котором мы должны спроситьgoroutine
Указывает, что первый элемент общего фрагмента был обновлен. Создайтеsync.Cond
нужноsync.Locker
объект (sync.Mutex
илиsync.RWMutex
):
cond := sync.NewCond(&sync.Mutex{})
Затем напишем функцию, отвечающую за отображение первого элемента слайса:
func printFirstElement(s []int, cond *sync.Cond) {
cond.L.Lock()
cond.Wait()
fmt.Printf("%d\n", s[0])
cond.L.Unlock()
}
мы можем использоватьcond.L
Доступ к внутреннему мьютексу. Как только замок будет получен, мы позвонимcond.Wait()
, что сделает текущийgoroutine
Блокируется до получения сигнала.
давай вернемсяmain goroutine
. Мы поделимся фрагментом, передав его и ранее созданныйsync.Cond
создаватьprintFirstElement
бассейн. Затем мы звонимget()
функция, сохраняющая результат вs[0]
в и сигнал:
s := make([]int, 1)
for i := 0; i < runtime.NumCPU(); i++ {
go printFirstElement(s, cond)
}
i := get()
cond.L.Lock()
s[0] = i
cond.Signal()
cond.L.Unlock()
Этот сигнал деактивируетgoroutine
заблокированное состояние, разблокированоgoroutine
покажетs[0]
значение, хранящееся в .
Однако некоторые могут возразить, что наш код неисправен.Go
Один из основных принципов:
Не общайтесь через общую память, вместо этого делитесь памятью через общение.
Действительно, в этом примере лучше использоватьchannel
пройтиget()
Возвращаемое значение. Но мы также упомянулиsync.Cond
Может также использоваться для широковещательных сигналов. Давайте изменим приведенный выше пример наSignal()
позвони вместо этогоBroadcast()
.
i := get()
cond.L.Lock()
s[0] = i
cond.Broadcast()
cond.L.Unlock()
В этом случае все горутины будут запущены.
Как мы все знаем,channel
Элементы в будут состоять только изgoroutine
получено. пройти черезchannel
Единственный выход - отключить аналоговое вещаниеchannel
.
Когда канал закрыт, после того, как данные, которые были отправлены в канал, были успешно получены, последующие операции приема больше не будут блокироваться, и они немедленно вернут нулевое значение.
Но таким образом можно транслировать только один раз. Поэтому, хотя существует много споров, несомненно,sync.Cond
интересная особенность .
Рекомендуемое чтение
Узнайте, как использовать контекст для отмены выполнения горутины.
Используйте SecureCookie для реализации управления сеансом на стороне клиента.
Перейти к веб-программированию — анализ запросов JSON и генерация ответов JSON