pellucid stm
DESCRIPTION
Lightning Talk on Software Transactional Memory in Scala. The Actors Pattern has gotten a lot of attention in the Scala ecosphere, but STM of often a good first solution for solving concurrency problems. It's odd that it hasn't had as much attention in the Scala world, so this talk aims to show how easy it is to use, and compare it to typical lock-based synchronization by showing how easy it is to make errors with lock-based synchronizationTRANSCRIPT
SOFTWARE TRANSACTIONAL MEMORY@DUSTINWHITNEY
Alternative to lock-based synchronization
Analogous to database transactions
ACI (ACID with out the ‘D’)
Simple! (well… simplier)
WHAT IS STM?
libraryDependencies += ("org.scala-stm" %% "scala-stm" %
"0.7")
import scala.concurrent.stm._
val protectedInt = Ref(0)
atomic{ implicit transaction=> val currentValue = protectedInt.get protectedInt.set(currentValue + 1)}
WHAT DOES IT LOOK LIKE?
ATOMIC
val protectedInt = Ref(0)val anotherProtectedInt = Ref(0)
atomic{ implicit transaction => val currentValue = protectedInt.get protectedInt.set(currentValue + 1) val anotherCurrentValue = anotherProtectedInt.get anotherProtectedInt.set(anotherCurrentValue + 1)}
CONSISTENT
val protectedInt = Ref(0)
atomic{ transaction => val currentValue = protectedInt.get(transaction) protectedInt.set(currentValue + 1)(transaction)}
val protectedInt = Ref(0)
atomic{ implicit transaction => val currentValue = protectedInt.get protectedInt.set(currentValue + 1)}
ISOLATED
STM is not Durable
DURABLE
ADVANTAGES: DEADLOCK / LIVELOCK
val protectedInt = Ref(0)
atomic{ implicit transaction => val currentValue = protectedInt.get protectedInt.set(currentValue + 1)}
ADVANTAGES: PRIORITY INVERSION
val protectedInt = Ref(0)
atomic{ implicit transaction => val currentValue = protectedInt.get protectedInt.set(currentValue + 1)}
ADVANTAGES: COMPOSABILITY
val protectedInt = Ref(0)val anotherProtedtedInt = Ref(0)
atomic{ implicit transaction => val currentValue = protectedInt.get protectedInt.set(currentValue + 1) atomic{ implicit transaction => val anotherCurrentValue = anotherProtectedInt.get val anotherProctedInt.set(anotherCurrentValue + 1) }
}
GOTCHAS: IMMUTABILITY!
// bad!val badMap= Ref(new java.util.HashMap[String, Int]())val mutableMap = atomic{ implicit transaction => badMap.get }mutableMap.put(“Wrong!”, 666)
// goodval goodMap = Ref(Map(“Good” -> 7))atomic{ implicit transaction => val tempMap = goodMap.get goodMap.set(tempMap + (“Good” -> 777))}
GOTCHAS: REFERENTIAL TRANSPARENCY
//badval protectedString = Ref("This is a string")val time = System.currentTimeMillisatomic{ implicit transaction => if(time % 2 == 0) protectedString.set("Time was even") else protectedString.set("Time was odd")}
//goodatomic{ implicit transaction => val time = System.currentTimeMillis if(time % 2 == 0) protectedString.set("Time was even") else protectedString.set("Time was odd")}
EXTENDED EXAMPLE: STM
case class Account(number: Int, balance: Int)
val accounts = Ref(Map( 1 -> Account(1, 100), 2 -> Account(2, 100) ))
def transfer(to: Int, from: Int, amount: Int){ atomic{ implicit transaction => val map = accounts.get val toAccount = map(to) val fromAccount = map(from) accounts.set( map + (to -> (toAccount.copy(balance = toAccount.balance + amount))) + (from -> (fromAccount.copy(balance = fromAccount.balance - amount))) ) } }
EXTENDED EXAMPLE: SYNCHRONIZED
import java.util._private val accounts = new HashMap[Int, Account]()accounts.put(1, Account(1, 100))accounts.put(2, Account(2, 100))
def transfer(to: Int, from: Int, amount: Int){ accounts.synchronized{ val toAccount = accounts.get(to) val fromAccount = accounts.get(from) accounts.put(to, (toAccount.copy(balance = toAccount.balance + amount))) accounts.put(from, (fromAccount.copy(balance = fromAccount.balance - amount))) }}
EXTENDED EXAMPLE: SYNCHRONIZED2
import java.util.concurrent._private val accounts = ConcurrentHashMap[Int, Account]()accounts.put(1, Account(1, 100))accounts.put(2, Account(2, 100))
def transfer(to: Int, from: Int, amount: Int){ val toAccount = accounts.get(to) val fromAccount = accounts.get(from) toAccount.synchronized{ fromAccount.synchronized{ accounts.put(to, (toAccount.copy(balance = toAccount.balance + amount))) accounts.put(from, (fromAccount.copy(balance = fromAccount.balance - amount))) } }}
EXTENDED EXAMPLE: SYNCHRONIZED3
import java.util.concurrent._private val accounts = ConcurrentHashMap[Int, Account]()accounts.put(1, Account(1, 100))accounts.put(2, Account(2, 100))
def transfer(to: Int, from: Int, amount: Int){ val toAccount = accounts.get(to) val fromAccount = accounts.get(from) val (firstLock, secondLock) = if(to > from) (toAccount, fromAccount) else (fromAccount, toAccount) firstLock.synchronized{ secondLock.synchronized{ accounts.put(to, (toAccount.copy(balance = toAccount.balance + amount))) accounts.put(from, (fromAccount.copy(balance = fromAccount.balance - amount))) } }}
Questions?