NIUCLOUD是一款SaaS管理后台框架多应用插件+云编译。上千名开发者、服务商正在积极拥抱开发者生态。欢迎开发者们免费入驻。一起助力发展! 广告
## 竞争条件 当我们能够没有办法自信地确认一个事件是在另一个事件的前面或者后面发生的话,就说明x和y这两个事件是并发的。 ## 并发安全 一个函数在线性程序中可以正确地工作。如果在**并发**的情况下,这个函数依然可以**正确地工作**的话,那么我们就说这个函数是并发安全的,并发安全的函数不需要额外的同步工作。我们可以把这个概念概括为一个特定类型的一些方法和操作函数,对于某个类型来说,如果其所有可访问的**方法和操作都是并发安全**的话,那么类型便是**并发安全的。** ## 哪些是并发不安全的 导出包级别的函数一般情况下都是并发安全的。由于**package级的变量没法被限制在单一的gorouine**,所以修改这些变量“必须”使用互斥条件。 也就是说,包级别的变量是并发不安全的,包括所有普通类型和slice,map,数组. ## 竞争条件 **竞争条件指的是程序在多个goroutine交叉执行操作时,没有给出正确的结果**。竞争条件是很恶劣的一种场景,因为这种问题会一直潜伏在你的程序里,然后在非常少见的时候蹦出来,或许只是会在很大的负载时才会发生,又或许是会在使用了某一个编译器、某一种平台或者某一种架构的时候才会出现。这些使得**竞争条件带来的问题非常难以复现而且难以分析诊断**。 ## 产生竞争条件的原因 所有对数据进行的操作,都会先拿到这个数据本身的值,然后再本身的值上面进行操作. 非并发情况下,肯定是顺序执行的.但是在并发条件下A先读S的值,此时B在A的后面也读到S的值了,然后B在S上+100,A也在S上+100.然后S的值就是100.因为B对S的操作丢失了. ``` S := 0 A读 //S = 0 B读 //S = 0 B写: S += 100 //S = 100 A写: S += 100 //S = 100 S最后就等于100了,因为A和B都不是在对方已经+100的基础上进行+100的.它们读到的都是0. ``` ## 数据竞争(非常重要的概念) 无论任何时候,只要有**两个或以上goroutine并发访问同一变量**,且至少其中的一个是**写操作**的时候就会发生数据竞争。 ## 一定要避免数据竞争 **一个好的经验法则是根本就没有什么所谓的良性数据竞争(不要心存幻想)**。 ## 避免数据竞争三种方式 ### 1.不去写变量 对于共享数据,所有的并发只读它,而不去修改它.(但是很多业务就是要写啊,所以这个方式不好) ### 2.避免多个goroutine访问变量 只能使用一个channel来发送给指定的goroutine请求来查询更新变量。这也就是Go的口头禅“不要使用共享数据来通信;使用通信来共享数据”。一个提供对一个指定的变量通过channel来请求的goroutine叫做这个变量的监控(monitor)goroutine。 下面的例子可以看的很清除,对于共享数据,专门使用一个goroutine来监控它数据操作的情况,所有的操作都是有顺序的. **这里的秘密就在于,读是可以随便读,而写则通过channel来进行线性化.这样就避免了数据竞争**. ~~~ package bank var deposits = make(chan int) // send amount to deposit var balances = make(chan int) // receive balance func Deposit(amount int) { deposits <- amount } func Balance() int { return <-balances } func teller() { var balance int // balance is confined to teller goroutine for { select { case amount := <-deposits: balance += amount case balances <- balance: } } } func init() { go teller() // start the monitor goroutine } ~~~ 即使**当一个变量无法在其整个生命周期内被绑定到一个独立的goroutine**,绑定依然是并发问题的一个解决方案。例如在一条流水线上的goroutine之间共享变量是很普遍的行为,在这两者间会通过channel来传输地址信息。如果流水线的每一个阶段都能够避免在将变量传送到下一阶段后再去访问它,那么对这个变量的所有访问就是线性的。其效果是变量会被绑定到流水线的一个阶段,传送完之后被绑定到下一个,以此类推。这种规则有时被称为串行绑定。 怎么理解上面的话呢?就是可能一个变量是在函数中的,在函数内部使用了并发程序去操作它,那么依然是可以通过channel来将共享的变量绑定在一个channel上.那么这样也是线性的.只要是在往这个channel发送该变量之前没有再去使用它. ### 3.互斥 允许很多goroutine去访问变量,但是在同一个时刻最多只有一个goroutine在访问。这种方式被称为“互斥”.