Go语言共享数据的安全性
Go语言保证协程间共享数据的安全性有两种方式,一种是给共享变量加锁,另一种是通过通道来共享数据。
下面通过简单的模拟火车票的售票窗口,来看一下这两种数据共享方式
第一种,通过通道共享
package main
import (
"fmt"
"sync"
"time"
)
var (
ticket = 1 //火车票票号
windNum = 1000 //窗口的数量
ticketTotal = 100000 //总票数
wg sync.WaitGroup
lk sync.Mutex
ch = make(chan int, ticketTotal)
)
func main() {
//先把10万张票写入通道
for i := 1; i <= ticketTotal; i++ {
ch <- i
}
//写入操作完毕,关闭通道
close(ch)
//等待1000个子协程执行完毕,再结束主函数。
wg.Add(windNum)
now := time.Now()
for i := 1; i <= windNum; i++ {
go saleTic(i)
}
wg.Wait()
end := time.Since(now)
fmt.Println(end)
}
func saleTic(n int) {
defer wg.Done()
for value := range ch {
fmt.Print("窗口", n, "售票:", value, "\n")
}
fmt.Println("窗口", n, ":车票已售罄。")
}
运行结果
窗口80售票:99268
窗口 80 :车票已售罄。
窗口 466 :车票已售罄。
窗口 715 :车票已售罄。
窗口 161 :车票已售罄。
窗口 989 :车票已售罄。
窗口550售票:92966
窗口 550 :车票已售罄。
窗口702售票:89661
窗口 702 :车票已售罄。
窗口 440 :车票已售罄。
356.392311ms
通过开启1000个协程,模拟火车票售票窗口,每个协程代表一个售票窗口,总共售出10万张火车票,最后356毫秒售完。
第二种方式
package main
import (
"fmt"
"sync"
"time"
)
var (
ticket = 1
windNum = 1000
ticketTotal = 100000
wg3 sync.WaitGroup
lk sync.Mutex
)
func main() {
wg3.Add(windNum)
now := time.Now()
for i := 1; i <= windNum; i++ {
go saleTickets(i)
}
wg3.Wait()
end := time.Since(now)
fmt.Println(end)
}
func saleTickets(n int) {
defer wg3.Done()
for i := 1; i <= ticketTotal; i++ {
lk.Lock()
if ticket <= ticketTotal {
fmt.Print("窗口", n, "售票:", ticket, "\n")
ticket++
} else {
fmt.Println("窗口", n, ":车票已售罄。")
lk.Unlock()
break
}
lk.Unlock()
}
}
运行结果
…………………省略…………………………………
窗口56售票:99994
窗口53售票:99995
窗口54售票:99996
窗口55售票:99997
窗口57售票:99998
窗口59售票:99999
窗口60售票:100000
窗口 46 :车票已售罄。
窗口 58 :车票已售罄。
窗口 61 :车票已售罄。
窗口 65 :车票已售罄。
窗口 62 :车票已售罄。
…………………省略…………………………………
窗口 52 :车票已售罄。
窗口 51 :车票已售罄。
窗口 47 :车票已售罄。
窗口 56 :车票已售罄。
窗口 53 :车票已售罄。
窗口 54 :车票已售罄。
窗口 55 :车票已售罄。
窗口 57 :车票已售罄。
窗口 59 :车票已售罄。
窗口 60 :车票已售罄。
415.865144ms
同样模拟1000个售票窗口,总共售出10万张票,最后456毫秒售完,这种最简单的模拟高并发,不管是通过加锁来保证共享数据安全,还是通过通道来共享数据,效率差不多,不过有一点区别是,加了锁之后,车票是按顺序售出的,车票是从1号到100000号依次被售卖的,不过具体是哪个窗口卖出的则不固定。
而通过通道来共享数据的话,售票的顺序不固定,第一张被售出的车票很可能不是1号,最后一张被售出的车票也不是100000号。
通过通道共享数据,安全性高,不会出现一张票被重复出售;而另一种方式,如果saleTickets函数不加锁,那么会出现第1张票可能被重复出售了几百次,这就是并发中的数据不安全性:当多个线程同时操作修改一个数据时,会发生同一个数字被多次使用的问题。
【举个栗子】,就好像你的银行账户上有10万块钱,你让你爸拿存折到银行柜台取钱这10万块钱的同时,你又拿着你的银行卡到自动取款机也取这10万,同时你女朋友又用你的绑定了这张银行卡的某宝账号在某宝上买了个包包,你们3人掐好了时间,同时按下了取款按钮或者点击了确认支付,那么会发生什么情况,你的这10万块钱被花了三次,也就是你卡上只有10万块钱,但是你却用这张卡一下消费了三十万,如果大家都这样搞,那我们天朝的宇宙四大行早就歇菜了,那么如何解决这个问题呢,就是加锁,银行系统会设定,当你在操作一个银行账号的时候,你往系统中输入了这个账号之后,系统就会锁定这个账号,其他任何人,在任何地点都不能再操作这个账号了,只有当你按下了取款按钮,自动取款机把钱吐出来,并且你按下了退卡按钮以后,银行系统才会解锁这个账户,这时其他人才能操作这个账号。
类似的,多线程中给共享变量加锁的效果就是,在一个线程操作这个数据的时候,其他线程不能在操作这个数据,只有当这个线程结束之后,其他线程才能再次操作这个共享变量,这样就保证了数据安全。