В предыдущем разделе мы упоминали, что различные сопрограммы параллельного программирования совместно используют данные в дополнение к каналам и общим переменным. Хотя язык Go официально рекомендует использовать каналы для обмена данными, совместное использование через переменные является основой, поскольку каналы также реализуются путем совместного использования переменных на нижнем уровне. Внутренняя структура данных канала содержит массив, а чтение и запись в канал — это чтение и запись внутреннего массива.
Чтобы совместно использовать переменные для чтения и записи в параллельной среде, необходимо использовать блокировки для управления безопасностью структур данных.В языке Go есть встроенный пакет синхронизации, который содержит часто используемый нами объект мьютекса sync.Mutex. Встроенный словарь языка Go не является потокобезопасным, поэтому давайте попробуем использовать объект мьютекса, чтобы защитить словарь и сделать его потокобезопасным.
словарь, небезопасный для потоков
Язык Go имеет встроенный инструмент «проверки гонки» структуры данных, который помогает нам проверить, есть ли в программе код, небезопасный для потоков. Когда мы запускаем код, включите переключатель -run, и программа выполнит скрытую проверку во встроенной общей структуре данных. Инструмент проверки гонок был представлен в Go версии 1.1. Эта функция помогла "мета-команде" языка Go найти десятки ошибок в стандартной библиотеке языка Go с потенциальными угрозами безопасности потоков. Это очень удивительная функция. В то же время это также показывает, что даже боги в мире обезьян не могут избежать ошибок в коде, который они пишут. Давай попробуем
package main
import "fmt"
func write(d map[string]int) {
d["fruit"] = 2
}
func read(d map[string]int) {
fmt.Println(d["fruit"])
}
func main() {
d := map[string]int{}
go read(d)
write(d)
}
Приведенный выше код явно имеет угрозу безопасности.Выполните следующую команду проверки гонки, чтобы увидеть результат вывода.
$ go run -race main.go
==================
WARNING: DATA RACE
Read at 0x00c420090180 by goroutine 6:
runtime.mapaccess1_faststr()
/usr/local/Cellar/go/1.10.3/libexec/src/runtime/hashmap_fast.go:172 +0x0
main.read()
~/go/src/github.com/pyloque/practice/main.go:10 +0x5d
Previous write at 0x00c420090180 by main goroutine:
runtime.mapassign_faststr()
/usr/local/Cellar/go/1.10.3/libexec/src/runtime/hashmap_fast.go:694 +0x0
main.main()
~/go/src/github.com/pyloque/practice/main.go:6 +0x88
Goroutine 6 (running) created at:
main.main()
~/go/src/github.com/pyloque/practice/main.go:15 +0x59
==================
==================
WARNING: DATA RACE
Read at 0x00c4200927d8 by goroutine 6:
main.read()
~/go/src/github.com/pyloque/practice/main.go:10 +0x70
Previous write at 0x00c4200927d8 by main goroutine:
main.main()
~/go/src/github.com/pyloque/practice/main.go:6 +0x9b
Goroutine 6 (running) created at:
main.main()
~/go/src/github.com/pyloque/practice/main.go:15 +0x59
==================
2
Found 2 data race(s)
Инструменты проверки гонки основаны на проверке кода во время выполнения, а не на статическом анализе кода. Это означает, что если в логике кода есть угроза безопасности, которая не может быть запущена, ее нельзя проверить.
Безопасный словарь
Пусть словарь становится безопасным потоком, вам необходимо прочитать и записать операции на все словари, используют защищенные Mutex.
package main
import "fmt"
import "sync"
type SafeDict struct {
data map[string]int
mutex *sync.Mutex
}
func NewSafeDict(data map[string]int) *SafeDict {
return &SafeDict{
data: data,
mutex: &sync.Mutex{},
}
}
func (d *SafeDict) Len() int {
d.mutex.Lock()
defer d.mutex.Unlock()
return len(d.data)
}
func (d *SafeDict) Put(key string, value int) (int, bool) {
d.mutex.Lock()
defer d.mutex.Unlock()
old_value, ok := d.data[key]
d.data[key] = value
return old_value, ok
}
func (d *SafeDict) Get(key string) (int, bool) {
d.mutex.Lock()
defer d.mutex.Unlock()
old_value, ok := d.data[key]
return old_value, ok
}
func (d *SafeDict) Delete(key string) (int, bool) {
d.mutex.Lock()
defer d.mutex.Unlock()
old_value, ok := d.data[key]
if ok {
delete(d.data, key)
}
return old_value, ok
}
func write(d *SafeDict) {
d.Put("banana", 5)
}
func read(d *SafeDict) {
fmt.Println(d.Get("banana"))
}
func main() {
d := NewSafeDict(map[string]int{
"apple": 2,
"pear": 3,
})
go read(d)
write(d)
}
Если вы попытаетесь запустить приведенный выше код с помощью инструмента проверки гонки, вы обнаружите, что сейчас не выводится ряд предупреждений, указывающих на то, что методы Get и Put достигли безопасности сопрограммы, но он все еще не может определить, является ли Delete() метод безопасен, потому что у него нет шансов запуститься.
В приведенном выше коде мы снова видим сценарий применения оператора defer — снятие блокировки. Оператор defer всегда откладывается для запуска в конце функции, поэтому, если логика функции выполняется в течение длительного времени, это приведет к тому, что блокировка будет удерживаться в течение длительного времени.В настоящее время это может быть не очень хорошей идеей. использовать оператор defer для снятия блокировки.
Избегайте дублирования блокировки
Есть еще один момент, на который следует обратить особое внимание в приведенном выше коде, это то, что sync.Mutex является структурным объектом, этот объект не должен копироваться в процессе использования - поверхностное копирование. Репликация вызовет «расщепление» блокировки, и она не будет играть защитной роли. Поэтому старайтесь максимально использовать его тип указателя при обычном использовании. Читатели могут попробовать заменить вышеуказанный тип типом без указателя, а затем запустить инструмент проверки гонки, и вы увидите, что предупреждающее сообщение снова заполняет весь экран. Копирование блокировки существует при назначении структурных переменных, передаче параметров функции и передаче параметров метода, и все они требуют внимания.
Используйте поля анонимной блокировки
В главе о структуре мы знаем, что внешняя структура может автоматически наследовать все методы анонимной внутренней структуры. Если вышеуказанную структуру SafeDict изменить и поле блокировки сделать анонимным, код можно немного упростить.
package main
import "fmt"
import "sync"
type SafeDict struct {
data map[string]int
*sync.Mutex
}
func NewSafeDict(data map[string]int) *SafeDict {
return &SafeDict{data, &sync.Mutex{}}
}
func (d *SafeDict) Len() int {
d.Lock()
defer d.Unlock()
return len(d.data)
}
func (d *SafeDict) Put(key string, value int) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
d.data[key] = value
return old_value, ok
}
func (d *SafeDict) Get(key string) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
return old_value, ok
}
func (d *SafeDict) Delete(key string) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
if ok {
delete(d.data, key)
}
return old_value, ok
}
func write(d *SafeDict) {
d.Put("banana", 5)
}
func read(d *SafeDict) {
fmt.Println(d.Get("banana"))
}
func main() {
d := NewSafeDict(map[string]int{
"apple": 2,
"pear": 3,
})
go read(d)
write(d)
}
Используйте замки для чтения-записи
В повседневных приложениях большинство параллельных структур данных чаще читаются и меньше записываются. В случаях, когда больше читается и меньше пишется, блокировка мьютекса может быть заменена блокировкой чтения-записи, что может эффективно повысить производительность. Пакет синхронизации также предоставляет объект блокировки чтения-записи RWMutex.В отличие от мьютекса, который имеет только два общих метода, Lock() и Unlock(), блокировка чтения-записи предоставляет четыре общих метода, а именно блокировку записи Lock() и запись освобождения.Блокировка разблокировки(), чтение блокировки RLock() и чтение освобождения блокировки RUnlock() Блокировка записи является эксклюзивной блокировкой. При добавлении блокировки записи она блокирует другие сопрограммы, а также блокировки чтения и блокировки записи. Блокировки чтения являются общими блокировками. Добавление блокировки чтения также может позволить другим сопрограммам добавлять блокировки чтения, но блокирует добавление блокировок записи.
Производительность блокировок чтения-записи ухудшается до уровня обычных блокировок мьютексов в случае высокого параллелизма записи. Далее трансформируем блокировку взаимного исключения SafeDict в коде в блокировку чтения-записи.
package main
import "fmt"
import "sync"
type SafeDict struct {
data map[string]int
*sync.RWMutex
}
func NewSafeDict(data map[string]int) *SafeDict {
return &SafeDict{data, &sync.RWMutex{}}
}
func (d *SafeDict) Len() int {
d.RLock()
defer d.RUnlock()
return len(d.data)
}
func (d *SafeDict) Put(key string, value int) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
d.data[key] = value
return old_value, ok
}
func (d *SafeDict) Get(key string) (int, bool) {
d.RLock()
defer d.RUnlock()
old_value, ok := d.data[key]
return old_value, ok
}
func (d *SafeDict) Delete(key string) (int, bool) {
d.Lock()
defer d.Unlock()
old_value, ok := d.data[key]
if ok {
delete(d.data, key)
}
return old_value, ok
}
func write(d *SafeDict) {
d.Put("banana", 5)
}
func read(d *SafeDict) {
fmt.Println(d.Get("banana"))
}
func main() {
d := NewSafeDict(map[string]int{
"apple": 2,
"pear": 3,
})
go read(d)
write(d)
}
В следующем разделе мы попробуем одну из самых сложных частей изучения языка Go — рефлексию.
Прочитайте больше глав «Быстро изучите язык Go», нажмите и удерживайте изображение, чтобы определить QR-код, и подпишитесь на общедоступную учетную запись «Code Cave».