Given the following implementation of the producer-consumer problem in scala
class PC {
var buffer = null.asInstanceOf[Int]
var set = false
def produce(value: Int) = synchronized {
while (set) wait()
buffer = value
set = true
notify() }
def consume: Int = synchronized {
while (!set) wait()
val result = buffer
set = false
notify()
return result; }
}
There are 3 things I don't quite manage to see:
Why, if I'm using notify instead of notifyAll, I end up in deadlock; Where should I use notifyAll, for in produce or consume?
Shouldn't I have an object e.g. lock, and call lock.synchronized, lock.wait and lock.notify? Why does it work like this, don't produce and consume have 2 different monitors associated? Why does 'notify' from produce notifies 'wait' from consume?
How does a monitor work exactly in scala (in our case)? Does it use a signal-and-continue policy? how do processes from a waiting queue on a certain condition get moved to a runnable queue? Is there a queue for each condition/lock (e.g. lock1.wait, lock2.wait, etc).
In the case of the multiThreading, notify() method sends the notification to only one thread among the multiple waiting threads which are waiting for the send lock. While notifyAll() methods in the same context send notifications to all waiting threads instead of a single thread.
If no threads are waiting in the waiting queue, then notify() and notifyAll() have no effect. Before calling the notify() or notifyAll() method of an object, a thread must own the lock of the object. Hence it must be in one of the object's synchronized methods or synchronized block.
If wait() and notify() were on the Thread instead then each thread would have to know the status of every other thread and there is no way to know thread1 that thread2 was waiting for any resource to access. Hence, notify, wait, notifyAll methods are defined in object class in Java.
The wait() is used in with notify() and notifyAll() methods, but join() is used in Java to wait until one thread finishes its execution. wait() is mainly used for shared resources, a thread notifies other waiting thread when a resource becomes free. On the other hand join() is used for waiting a thread to die.
This is really a question mostly about Java concurrency. Scala concurrency is built on top of the Java concurrency model, but the syntax differs. In Scala, synchronized
is a method of AnyRef
, and the above syntax is equivalent to using the keyword synchronized
to write synchronized methods as in the following Java code:
public class PC {
public int buffer;
public boolean set;
public synchronized void produce(int value) {
while(set) wait();
buffer = value;
set = true;
notify();
}
public synchronized int def consume {
while(!set) wait();
int result = buffer;
notify();
return result;
}
}
For more detailed coverage of the Java concurrency model, peruse the Java Tutorials. You may want to investigate the Java Concurrency Library. For instance, you could implement the same thing using a Blocking Queue with a capacity of 1.
In answer to your questions:
1. Why, if I'm using notify instead of notifyAll, I end up in deadlock; Where should I use notifyAll, for in produce or consume?
You probably have a race condition (rather than a deadlock) because the notify()
in the consumer()
is received by some other consumer thread, rather than the producer thread, but that's just a guess. As far as whether to use notify()
or notifyAll()
and in which methods, some recommend that notifyAll()
always be used. And, in this case, you can use notifyAll()
, because you are waiting in a conditional while loop -- which you should always do for various reasons described in the documentation of wait(). However, you could also choose to use notify()
as an optimization in the producer()
, because I'm assuming you only want one consumer to consume the buffer contents. With the current implementation, you must still use notifyAll()
in consume()
or potentially expose yourself to a situation where one of the consumers is notified rather than the single waiting producer resulting in the producer waiting forever.
2. Shouldn't I have an object e.g. lock, and call lock.synchronized, lock.wait and lock.notify? Why does it work like this, don't produce and consume have 2 different monitors associated? Why does 'notify' from produce notifies 'wait' from consume?
You do have a lock. It's an implicit lock on the PC
instance and in Java there's one and only one monitor per object although there may be many entry points. The wait()
in consume()
is notified by the notify()
in produce()
, because they are both waiting for a lock on the same resource -- the PC
instance. If you want to implement more flexible or finer-grained locking, then you can use various strategies from the the Java Concurrency Library such as Locks.
3. How does a monitor work exactly in scala (in our case)? Does it use a signal-and-continue policy? how do processes from a waiting queue on a certain condition get moved to a runnable queue? Is there a queue for each condition/lock (e.g. lock1.wait, lock2.wait, etc).
For a good description of how the JVM performs thread synchronization, read this: How the Java virtual machine performs thread synchronization. For bit more detail from a chapter from the same author, you can read Inside the Java Virtual Machine.
As you may have observed, the problem you are witnessing does not occur if there is only one producer and one consumer (because in that case, notify
does the job you expect it to do, which is to let the next producer/consumer make its move).
If you have more than one producer or consumer however, the following problem occurs: Lets say there are 2 producers and a single consumer. In this scenario, the following is bound to happen if you use notify()
:
notify()
notify()
now waits infinitely, because the consumer will never get notifiedIf notifyAll
is called instead, there will always be a consumer which gets notified, thus the problem that a producer or consumer waits indefinitely for the other party neverr arises.
Your lock object is the PC
object. A Scala object
is simply a singleton instance of a class which the compiler generates for you. Since your object
is actually a class instance of the class Object
, it also inherits its notify
, notifyAll
and wait
methods.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With