在 Go 语言中,正确的使用并发(2)

xferChan <- xfer
        err := <-errChan
        if err == nil  {
            server.UpdateBalances(payer, payee) // Still magic.
        }
        return err
    }

这里有更多代码,但是我们通过实现一个微不足道的事件循环消除并发问题。当代码首次执行时,它激活一个goroutine运行循环。转发请求为了此目的而传递入一个新创建的通道。结果经由一个错误通道返回到循环外部。因为通道不是缓冲的,它们加锁,并且通过Transfer函数无论多个并发的转发请求怎么进,它们都将通过单一的运行事件循环被持续的服务。

上面的代码看起来有点别扭,也许吧. 对于这样一个简单的场景一个互斥锁(mutex)也许会是一个更好的选择,但是我正要尝试去证明的是可以向一个go例程应用隔离状态操作. 即使稍稍有点尴尬,但是对于大多数需求而言它的表现已经足够好了,并且它工作起来,甚至使用了最简单的账号结构实现:

type Account struct {
        balance float64
    }

func (a *Account) Balance() float64 {
        return a.balance
    }

func (a *Account) Deposit(amount float64) {
        log.Printf("depositing: %f", amount)
        a.balance += amount
    }

func (a *Account) Withdraw(amount float64) {
        log.Printf("withdrawing: %f", amount)
        a.balance -= amount
    }

不过如此笨拙的账户实现看起来会有点天真. 通过不让任何大于当前平衡的撤回操作执行,从而让账户结构自身提供一些保护也许更起作用。那如果我们把撤回函数变成下面这个样子会怎么样呢?:

func (a *Account) Withdraw(amount float64) {
        if amount > a.balance {
            log.Println("Insufficient funds")
            return
        }
        log.Printf("withdrawing: %f", amount)
        a.balance -= amount
    }

不幸的是,这个代码患有和我们原来的 Transfer 实现相同的问题。并发执行或不幸的上下文切换意味着我们可能以负平衡结束。幸运的是,内部的事件循环理念应用在这里同样很好,甚至更好,因为事件循环 goroutine 可以与每个个人账户结构实例很好的耦合。这里有一个例子说明这一点:

type Account struct {
        balance float64
        deltaChan chan float64
        balanceChan chan float64
        errChan chan error
    }

func NewAccount(balance float64) (a *Account) {
        a = &Account{
            balance:    balance,
            deltaChan:  make(chan float64),
            balanceChan: make(chan float64),
            errChan:    make(chan error),
        }
        go a.run()
        return
    }

func (a *Account) Balance() float64 {
        return <-a.balanceChan
    }

func (a *Account) Deposit(amount float64) error {
        a.deltaChan <- amount
        return <-a.errChan
    }

func (a *Account) Withdraw(amount float64) error {
        a.deltaChan <- -amount
        return <-a.errChan
    }

func (a *Account) applyDelta(amount float64) error {
        newBalance := a.balance + amount
        if newBalance < 0 {
            return errors.New("Insufficient funds")
        }
        a.balance = newBalance
        return nil
    }

func (a *Account) run() {
        var delta float64
        for {
            select {
            case delta = <-a.deltaChan:
                a.errChan <- a.applyDelta(delta)
            case a.balanceChan <- a.balance:
                // Do nothing, we've accomplished our goal w/ the channel put.
            }
        }
    }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/7dfa1354558b02b073eb7489d48284d0.html