golanggoroutinesGOMAXPROCSgoroutinesGOMAXPROCSgoroutines
并行执行安全性案例分析
利用计算机多核处理的特性,并行执行能成倍的提高程序的性能,但同时也带入了数据安全性问题,下面看一个在线银行转账的案例:
type User struct {
Cash int
}
func (u *User) sendCash(to *User, amount int) bool {
if u.Cash < amount {
return false
}
/* 设置延迟Sleep,当多个goroutines并行执行时,便于进行数据安全分析 */
time.Sleep(500 * time.Millisecond)
u.Cash = u.Cash - amount
to.Cash = to.Cash + amount
return true
}
func main() {
me := User{Cash: 500}
you := User{Cash: 500}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
me.sendCash(&you, 50) //转账
fmt.Fprintf(w, "I have $%d\n", me.Cash)
fmt.Fprintf(w, "You have $%d\n", you.Cash)
fmt.Fprintf(w, "Total transferred: $%d\n", (you.Cash - 500))
})
http.ListenAndServe(":8080", nil)
}
这是一个通用的Go Web应用,定义User数据结构,sendCash是在两个User之间转账的服务,这里使用的是net/http 包,我们创建了一个简单的Http服务器,然后将请求路由到转账50元的sendCash方法,在正常操作下,代码会如我们预料一样运行,每次转移50美金,一旦一个用户的账户余额达到0美金,就不能再进行转出钞票了,因为没有钱了,但是,如果我们很快地发送很多请求,这个程序会继续转出很多钱,导致账户余额为负数。
这是课本上经常谈到的竞争情况race condition,在这个代码中,账户余额的检查是与从账户中取钱操作分离的,我们假想一下,如果一个请求刚刚完成账户余额检查,但是还没有取钱,也就是没有减少账户余额数值;而另外一个请求线程同时也检查账户余额,发现账户余额还没有剩为零(结果两个请求都一起取钱,导致账户余额为负数),这是典型的”check-then-act”竞争情况。这是很普遍存在的 并发 bug。
用锁解决竟态数据安全问题
golangsync
type User struct {
Cash int
}
var transferLock *sync.Mutex
func (u *User) sendCash(to *User, amount int) bool {
transferLock.Lock() //对转账操作进行加锁
defer transferLock.Unlock() //转账结束后解锁释放资源
if u.Cash < amount {
return false
}
/* 设置延迟Sleep,当多个goroutines并行执行时,便于进行数据安全分析 */
time.Sleep(500 * time.Millisecond)
u.Cash = u.Cash - amount
to.Cash = to.Cash + amount
return true
}
func main() {
me := User{Cash: 500}
you := User{Cash: 500}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
me.sendCash(&you, 50) //转账
fmt.Fprintf(w, "I have $%d\n", me.Cash)
fmt.Fprintf(w, "You have $%d\n", you.Cash)
fmt.Fprintf(w, "Total transferred: $%d\n", (you.Cash - 500))
})
http.ListenAndServe(":8080", nil)
}
利用Channel,更好的实现并发
程序并发的性能Channel并发
type User struct {
Cash int
}
type Transfer struct {
Sender *User
Recipient *User
Amount int
}
func sendCashHandler(transferchan chan Transfer) {
var val Transfer
for {
val = <-transferchan
val.Sender.sendCash(val.Recipient, val.Amount)
}
}
func (u *User) sendCash(to *User, amount int) bool {
if u.Cash < amount {
return false
}
/* 设置延迟Sleep,当多个goroutines并行执行时,便于进行数据安全分析 */
time.Sleep(500 * time.Millisecond)
u.Cash = u.Cash - amount
to.Cash = to.Cash + amount
return true
}
func main() {
me := User{Cash: 500}
you := User{Cash: 500}
transferchan := make(chan Transfer)
go sendCashHandler(transferchan)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
transfer := Transfer{Sender: &me, Recipient: &you, Amount: 50}
transferchan <- transfer
fmt.Fprintf(w, "I have $%d\n", me.Cash)
fmt.Fprintf(w, "You have $%d\n", you.Cash)
fmt.Fprintf(w, "Total transferred: $%d\n", (you.Cash - 500))
})
http.ListenAndServe(":8080", nil)
并发DoS(Denial of Service服务拒绝)DoS
祭出select 进一步提升性能
select
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
transfer := Transfer{Sender: &me, Recipient: &you, Amount: 50}
/*转账*/
result := make(chan int)
go func(transferchan chan<- Transfer, transfer Transfer, result chan<- int) {
transferchan <- transfer
result <- 1
}(transferchan, transfer, result)
/*用select来处理超时机制*/
select {
case <-result:
fmt.Fprintf(w, "I have $%d\n", me.Cash)
fmt.Fprintf(w, "You have $%d\n", you.Cash)
fmt.Fprintf(w, "Total transferred: $%d\n", (you.Cash - 500))
case <-time.After(time.Second * 10): //超时处理
fmt.Fprintf(w, "Your request has been received, but is processing slowly")
}
})
DoS攻击并发
DApp开源社区,共享创意
文章出处: