原创做者,公众号【程序员读书】,欢迎关注公众号,转载文章请注明出处哦。程序员
应该说,不管使用哪种编程语言开发应用程序,并发编程都是复杂的,而Go语言内置的并发支持,则让Go并发编程变得很简单。编程
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)
}
复制代码
上面的例子叫银行存款问题,是演示并发的经典例子。并发
通常咱们认为,这个例子的运行结果只有三种:编程语言
上面的状况,都是假设存款操做是顺序的,可是,还存在一种状况,也就是小王或小张并发执行存款操做,这时候会发生存款金额丢失的风险。函数
看到上面的例子以后,咱们知道数据竞争会产生严重的后果,那如何避免数据竞争呢?有三种:ui
咱们可使用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()//释放锁
}
复制代码
当咱们使用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)
}
复制代码
固然,在实际项目并发编程的时候,咱们遇到的状况要远比上述例子复杂得多,所以还要多多练习,让本身对并发有更学层次的理解。
你的关注,是我写做路上最大的鼓励!