Reputation: 1897
Note: The problem that I solve has only educational purpose, I know that abstraction that I want to create is error prone and so on... I don't need fast solution, I need explanation.
In the book I am reading there is exercise that says that I need to implement SyncVar which has the following interface:
class SyncVar[T] {
def get(): T = ???
def put(x: T): Unit = ???
}
My comment: Alright seems understandable, need some sync variable that I can put or get.
A SyncVar object is used to exchange values between two or more threads. When created, the SyncVar object is empty:
° Calling get throws an exception
° Calling put adds a value to the SyncVar object After a value is added to a SyncVar object, we can say that it is non-empty:
° Calling get returns the current value, and changes the state to empty
° Calling put throws an exception
My thoughts: This is variable that throws exception on empty value when calling get, or put when we have a value, when we call get it clears previous value. Seems like I need to use Option.
So I provide the following implementation:
class SyncVar[T] {
var value: Option[T] = None
def get(): T = value match {
case Some(t) => this.synchronized {
value = None
t
}
case None => throw new IllegalArgumentException("error get")
}
def put(x: T): Unit = this.synchronized{
value match {
case Some(t) => throw new IllegalArgumentException("error put")
case None => value = Some(x)
}
}
def isEmpty = value.isEmpty
def nonEmpty = value.nonEmpty
}
My comment: Synchronously invoking put and get, also have isEmpty and nonEmpty
The next task makes me confused: The SyncVar object from the previous exercise can be cumbersome to use, due to exceptions when the SyncVar object is in an invalid state. Implement a pair of methods isEmpty and nonEmpty on the SyncVar object. Then, implement a producer thread that transfers a range of numbers 0 until 15 to the consumer thread that prints them.
As I understand I need two threads:
//producer thread that produces numbers from 1 to 15
val producerThread = thread{
for (i <- 0 until 15){
println(s"$i")
if (syncVar.isEmpty) {
println(s"put $i")
syncVar.put(i)
}
}
}
//consumer that prints value from 0 to 15
val consumerThread = thread{
while (true) {
if (syncVar.nonEmpty) println(s"get ${syncVar.get()}")
}
}
Question: But this code caused by nondeterminism, so it has different result each time, while I need to print numbers from 1 to 15 (in right order). Could you explain me what is wrong with my solution?
Upvotes: 0
Views: 160
Reputation: 1897
Thanks to @Alexey Romanov, finally I implement transfer method:
Explanation:
The idea is, that producer thread checks is syncVar
is empty, if it is it puts it, otherwise it waits with while(syncVar.nonEmpty){}
(using busy waiting, which is bad practice, but it is important to know about it in educational purpose) and when we leaving the loop(stop busy waiting) we putting variable and leaving for
loop for i == 0. Meanwhile consumer thread busy waiting forever, and reads variable when it is nonEmpty.
Solution:
def transfer() = {
val syncVar = new SyncVar[Int]
val producerThread = thread{
log("producer thread started")
for (i <- 0 until 15){
if (syncVar.isEmpty) {
syncVar.put(i)
} else {
while (syncVar.nonEmpty) {
log("busy wating")
}
if (syncVar.isEmpty) {
syncVar.put(i)
}
}
}
}
val consumerThread = thread{
log("consumer thread started")
while (true) {
if (syncVar.nonEmpty) {
syncVar.get()
}
}
}
}
Upvotes: 1
Reputation: 170899
First, your synchronized
in get
is too narrow. It should surround the entire method, like in put
(can you think why?).
After fixing, consider this scenario:
producerThread
puts 0 into syncVar
.
producerThread
continues to run and tries to put 1. syncVar.isEmpty
returns false
so it doesn't put 1. It continues to loop with next i
instead.
consumerThread
gets 0.
producerThread
puts 2.
Etc. So consumerThread
can never get and print 1, because producerThread
never puts it there.
Think what producerThread
should do if syncVar
is not empty and what consumerThread
should do if it is.
Upvotes: 1