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开源社区,共享创意

文章出处: