Параллельные переменные общей памяти Golang

Go

Оригинальный автор, публичный аккаунт [программист чтение], прошу обратить внимание на паблик-аккаунт, просьба указывать источник перепечатываемой статьи.

Следует сказать, что независимо от того, какой язык программирования используется для разработки приложения, параллельное программирование является сложным, а встроенная поддержка параллелизма языка Go делает параллельное программирование на Go очень простым.

CSP

CSP, то есть процесс последовательной связи, представляет собой модель параллелизма, изначально поддерживаемую языком Go. Обычно она реализуется с помощью горутины и канала. Идея программирования CSP состоит в том, чтобы «разделять память посредством связи, а не через общую память». Поэтому идея CSP используется для разработки параллельных программ, обычно используют каналы для последовательного соединения нескольких горутин и, наконец, достигают цели последовательного выполнения нескольких горутин.

Общая переменная памяти

Мы знаем, что один код гоутины выполняется последовательно, и во время параллельного программирования создается несколько горутин, но мы не можем определить порядок выполнения между разными горутинами.Большинство случаев между несколькими горутинами — это перекрестное выполнение кода.Во время процесса разделяемая память переменные могут быть изменены или прочитаны, что создаст数据竞争, произошли некоторые неожиданные результаты.

package main

var balance int
//存款
func Deposit(amount int) { 
    balance = balance + amount 
}
//读取余额
func Balance() int {
    return balance
}

func main(){
    //小王:存600,并读取余额
    go func(){
        Deposit(600)
        fmt.Println(Balance())
    }()
    //小张:存500
    go func(){
        Deposit(500)
    }()
    
    time.Sleep(time.Second)
    fmt.Println(balance)
}

Приведенный выше пример называется проблемой банковского депозита и является классическим примером параллелизма.

Как правило, мы думаем, что в этом примере есть только три вида бегущих результатов:

  1. Сяо Ван сохраняет 600, Сяо Ван читает баланс как 600, а Сяо Чжан вносит еще 500, общая сумма составляет 1100.
  2. Сяо Чжан сохраняет 500, Сяо Ван сохраняет 600, Сяо Ван читает баланс 500, и общая сумма составляет 1100.
  3. Сяо Ван сохраняет 600, Сяо Чжан сохраняет 500, Сяо Ван читает баланс 500, и общая сумма составляет 1100.

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

Замок

Увидев приведенный выше пример, мы знаем, что конкуренция данных будет иметь серьезные последствия, так как же избежать конкуренции данных? Есть три вида:

  1. Подключайте горутины через каналы, чтобы добиться эффекта последовательного выполнения и избежать конкуренции.
  2. Не изменять общие переменные в параллельных программах, что, конечно, маловероятно.
  3. При использовании блокировок только одна горутина может одновременно изменять переменные в памяти, а поведение взаимного исключения происходит, когда переменные изменяются разными горутинами.

Sync.mutex: Mutex.

Мы можем использовать мьютекс, предоставляемый языком Go, чтобы избежать описанного выше поведения гонки данных, и мы можем соответствующим образом изменить код:

mu sync.Mutex // 声明一个互斥锁
func Deposit(amount int) {
    mu.Lock()//获取锁
    balance = balance + amount
    mu.Unlock()//释放锁
}
//读取余额
func Balance() int {
    mu.Lock()//获取锁
    return balance
    mu.Unlock()//释放锁
}

sync.RWMutex: блокировка чтения-записи

Когда мы используем блокировку мьютекса Mutex, то независимо от того, читает он или изменяет, нам нужно ждать, пока другие горутины снимут блокировку, но чтение относительной модификации является безопасной операцией Go предоставляет другой тип блокировки, sync.RWMutex, чтение-запись lock, этот тип блокировки при множественном чтении не будет блокироваться, только при изменении вам нужно подождать, пока все блокировки чтения будут сняты перед модификацией, поэтому мы можем изменить функцию Balance() на:

rmu sync.RWMutex
func Balance() int {
    rmu.RLock()//获取读锁
    return balance
    rmu.RUnlock()//释放读锁
}

Лучшее использование замков

В приведенном выше примере мы все снимаем блокировку после функции, но в реальной разработке код функции очень длинный и есть разные суждения, мы не можем гарантировать, что функция сможет выполниться до конца и успешно снять блокировку. Если возникает ошибка, Невозможно снять блокировку, другие горутины блокируются, поэтому вы можете использовать ключевое слово defer, чтобы позволить функции снять блокировку в любом случае.

package main
import "sync"

var balance int
mu sync.Mutex // 声明一个互斥锁
rmu sync.RWMutex
//存款
func Deposit(amount int) {
    mu.Lock()//获取锁
    balance = balance + amount
    mu.Unlock()//释放锁
}
//读取余额
func Balance() int {
    rmu.RLock()//获取读锁
    return balance
    rmu.RUnlock()//释放读锁
}

func main(){
    //小王:存600,并读取余额
    go func(){
        Deposit(600)
        fmt.Println(Balance())
    }()
    //小张:存500
    go func(){
        Deposit(500)
    }()
    
    time.Sleep(time.Second)
    fmt.Println(balance)
}

Суммировать

Конечно, в случае параллельного программирования в реальных проектах ситуация, с которой мы сталкиваемся, намного сложнее, чем в приведенных выше примерах, поэтому нам нужно больше практиковаться, чтобы иметь более глубокое понимание параллелизма.

Ваше внимание — самое большое поощрение на моем писательском пути!