I'm not sure if my lock usage is correct and safe. I wanted to know what will be best approach to deal with situation when one thread have to wait for being initialized by another so I written this test:
import java.util.concurrent.locks.ReentrantLock
import java.util.{TimerTask, Timer, Date}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration.Duration
class I{
private var i:Int = 0
def inc() = i+=1
def value = i
}
trait Test {
def doSomething():Unit
def initialize(i:I):Unit
def result:Int
}
case class TestLocks() extends Test{
var ref:I = null
val lock = new ReentrantLock
val refInitialized = lock.newCondition()
def doSomething(){
lock.lock()
try {
if(ref==null) { refInitialized.await() }
ref.inc()
}
finally {
lock.unlock()
}
}
def initialize(i:I){
lock.lock()
try{
ref = i
refInitialized.signal()
}
finally {
lock.unlock()
}
}
def result = ref.value
}
case class TestPromises() extends Test{
val iPromise = concurrent.promise[I]
def doSomething(){
val i = Await.result(iPromise.future,Duration.Inf)
i.inc()
}
def initialize(i:I){
iPromise.success(i)
}
def result = Await.result(iPromise.future,Duration.Inf).value
}
object TestRunner{
def apply(test:Test):Long = {
val initializationTimeout = 10
val timer = new Timer()
timer.schedule(new TimerTask {
def run(){
test.initialize(new I)
}
},initializationTimeout)
val start = new Date().getTime
1 to 1000000 foreach (i=>{
test.doSomething()
})
val end = new Date().getTime
val duration = end-start-initializationTimeout
// Console.println(test+" result: "+test.result+" in: "+duration+"ms")
duration
}
}
object Tests {
def run() {
def average(l: => Long) =
{
val vals = 1 to 100 map(_=>{l.toDouble})
vals.sum/vals.length
}
def averageAsync(l: => Long) =
{
import ExecutionContext.Implicits.global
val futs = 1 to 100 map(_=>{Future{l.toDouble}})
val valsFut = Future.sequence(futs)
val vals = Await.result(valsFut,Duration.Inf)
vals.sum/vals.length
}
Console.println("Locks: "+average(TestRunner(TestLocks()))+"ms")
Console.println("Promises: "+average(TestRunner(TestPromises()))+"ms")
Console.println("A Locks: "+averageAsync(TestRunner(TestLocks()))+"ms")
Console.println("A Promises: "+averageAsync(TestRunner(TestPromises()))+"ms")
}
}
which outputs:
Locks: 28.0ms
Promises: 68.5ms
A Locks: 49.87ms
A Promises: 2307.84ms
what indicates that I have to use locks in "hot zones" and now I want to ensure if those tests are correct. Or maybe I should handle this "wait if uninitialized" case in any other way?
1 Answer 1
A completely different approach to your answer is a concept called Actors.
Rather than having multiple threads, and having these threads await each other, Actors are like tiny little processes that communicate with each other by sending messages. When you're done with a piece of work, you can send a message to another actor, telling it to go and do something else.
The Actor Model as it's known is both highly performant and simple to reason about.
Scala is fortunate enough to have an incredible implementation of the Actor Model known as Akka .
Akka allows you to very simply build large and complex systems - it can even handle distribution across many machines!
Here's a really simple example from the Akka website:
case class Greeting(who: String)
class GreetingActor extends Actor with ActorLogging {
def receive = {
case Greeting(who) ⇒ log.info("Hello " + who)
}
}
val system = ActorSystem("MySystem")
val greeter = system.actorOf(Props[GreetingActor], name = "greeter")
greeter ! Greeting("Charlie Parker")
How easy is that? :-)
I strongly recommend using the Actor Model instead of using conventional threads and locks. Scala provides us with an incredibly powerful toolkit, I encourage you to use it!
Explore related questions
See similar questions with these tags.