Golang并发之共享内存变量

原创做者,公众号【程序员读书】,欢迎关注公众号,转载文章请注明出处哦。程序员

应该说,不管使用哪种编程语言开发应用程序,并发编程都是复杂的,而Go语言内置的并发支持,则让Go并发编程变得很简单。编程

CSP

CSP,即顺序通讯进程,是Go语言中原生支持的并发模型,通常使用goroutine和channel来实现,CSP的编程思想是“经过通讯共享内存,而不是经过共享内存来通讯”,所以使用CSP思想来开发并发程序,通常是使用channel串联多个goroutine,最终达到多个goroutine顺序执行的目的。安全

共享内存变量

咱们知道,单个的goutine代码是顺序执行,而并发编程时,建立多个goroutine,但咱们并不能肯定不一样的goroutine之间的执行顺序,多个goroutine之间大部分状况是代码交叉执行,在执行过程当中,可能会修改或读取共享内存变量,这样就会产生数据竞争,发生一些意外以外的结果。bash

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

上面的状况,都是假设存款操做是顺序的,可是,还存在一种状况,也就是小王或小张并发执行存款操做,这时候会发生存款金额丢失的风险。函数

看到上面的例子以后,咱们知道数据竞争会产生严重的后果,那如何避免数据竞争呢?有三种:ui

  1. 经过channel串联goroutine,达到顺序执行的效果,避免竞争。
  2. 不在并发程序中修改共享变量,这固然是不太可能的状况。
  3. 经过使用锁,使用同一时间只有一个goroutine能够修改内存中的变量,也就使用不一样goroutine修改变量时发生互斥行为。

sync.Mutex:互斥锁

咱们可使用Go语言提供的互斥锁来避免上述的数据竞争行为的发生,能够把代码进行相应的修改:spa

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互斥锁的时候,那么不管是读取仍是修改,都须要等待其余goroutine释放锁,可是读取相对修改来是,是安全的操做,Go提供了另一种锁,sync.RWMutex,读写锁,这种锁,多个读取的时候,不会锁,只有修改时候,须要等到全部读取的锁释放,才能修改,因此咱们能够把Balance()函数修改成:code

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

更好地使用锁

上面的例子中,咱们都是在函数后面释放锁的,但实际开发中,函数的代码很长,有各类判断,咱们没法保证函数能执行到最后,并成功释放锁,若是中发生错误,没法释放锁,就形成其余goroutine的阻塞,所以可使用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)
}
复制代码

总结

固然,在实际项目并发编程的时候,咱们遇到的状况要远比上述例子复杂得多,所以还要多多练习,让本身对并发有更学层次的理解。

你的关注,是我写做路上最大的鼓励!

相关文章
相关标签/搜索