Skip to content

Instantly share code, notes, and snippets.

@tux21b
Created September 14, 2011 15:03
Show Gist options
  • Save tux21b/1216808 to your computer and use it in GitHub Desktop.
Save tux21b/1216808 to your computer and use it in GitHub Desktop.
Shared vs. Isolated Mutability
// Fictional accounting example #1 - "Shared Mutability"
package main
import (
"fmt"
"sync"
)
type Account struct {
id string
balance uint
mu sync.RWMutex
}
const (
MinBalance = 0
MaxBalance = 10000
)
func NewAccount(id string) *Account {
return &Account{id: id}
}
func (a *Account) Id() string {
return a.id
}
func (a *Account) Deposit(amount uint) bool {
a.mu.Lock()
defer a.mu.Unlock()
if a.balance+amount <= MaxBalance {
a.balance += amount
return true
}
return false
}
func (a *Account) Withdraw(amount uint) bool {
a.mu.Lock()
defer a.mu.Unlock()
if a.balance >= MinBalance+amount {
a.balance -= amount
return true
}
return false
}
func (a *Account) Balance() uint {
a.mu.RLock()
defer a.mu.RUnlock()
return a.balance
}
func Transfer(src, dst *Account, amount uint) bool {
// Always lock accounts in the same order to avoid deadlocks
if src.Id() < dst.Id() {
src.mu.Lock()
defer src.mu.Unlock()
dst.mu.Lock()
defer dst.mu.Unlock()
} else if dst.Id() < src.Id() {
dst.mu.Lock()
defer dst.mu.Unlock()
src.mu.Lock()
defer src.mu.Unlock()
} else {
panic("invalid transfer")
}
if src.balance-amount >= 0 {
src.balance -= amount
dst.balance += amount
return true
}
return false
}
func main() {
acc1 := NewAccount("Account1")
acc2 := NewAccount("Account2")
acc1.Deposit(100)
acc2.Deposit(20)
fmt.Printf("Balance of %s: %d\n", acc1.Id(), acc1.Balance())
fmt.Printf("Balance of %s: %d\n", acc2.Id(), acc2.Balance())
fmt.Println("Transfering money...")
Transfer(acc1, acc2, 50)
fmt.Printf("Balance of %s: %d\n", acc1.Id(), acc1.Balance())
fmt.Printf("Balance of %s: %d\n", acc2.Id(), acc2.Balance())
}
// Fictional accounting example #2 - "Isolated Mutability"
package main
import (
"fmt"
)
type Account struct {
id string
balance int
qry chan accQuery
}
const (
MinBalance = 0
MaxBalance = 10000
)
type accQuery struct {
amount int
reply chan accReply
}
type accReply struct {
ok bool
balance int
}
func NewAccount(id string) *Account {
a := &Account{id: id, qry: make(chan accQuery)}
go func() {
for msg := range a.qry {
if a.balance+msg.amount <= MaxBalance &&
a.balance+msg.amount >= MinBalance {
a.balance += msg.amount
msg.reply <- accReply{true, a.balance}
} else {
msg.reply <- accReply{false, a.balance}
}
}
}()
return a
}
func (a *Account) Id() string {
return a.id
}
func (a *Account) Deposit(amount int) bool {
reply := make(chan accReply)
a.qry <- accQuery{amount, reply}
return (<-reply).ok
}
func (a *Account) Withdraw(amount int) bool {
reply := make(chan accReply)
a.qry <- accQuery{-amount, reply}
return (<-reply).ok
}
func (a *Account) Balance() int {
reply := make(chan accReply)
a.qry <- accQuery{0, reply}
return (<-reply).balance
}
func Transfer(src, dst *Account, amount int) bool {
// TODO: Coordinate the transaction and make it atomic... How? :)
src.Withdraw(amount)
dst.Deposit(amount)
return false
}
func main() {
acc1 := NewAccount("Account1")
acc2 := NewAccount("Account2")
acc1.Deposit(100)
acc2.Deposit(20)
fmt.Printf("Balance of %s: %d\n", acc1.Id(), acc1.Balance())
fmt.Printf("Balance of %s: %d\n", acc2.Id(), acc2.Balance())
fmt.Println("Transfering money...")
Transfer(acc1, acc2, 50)
fmt.Printf("Balance of %s: %d\n", acc1.Id(), acc1.Balance())
fmt.Printf("Balance of %s: %d\n", acc2.Id(), acc2.Balance())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment