Slow Harry
Slow Harry

Reputation: 1897

SyncVar transfer producer/consumer threads in scala

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

Answers (2)

Slow Harry
Slow Harry

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

Alexey Romanov
Alexey Romanov

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:

  1. producerThread puts 0 into syncVar.

  2. 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.

  3. consumerThread gets 0.

  4. 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

Related Questions