Synchronization Via Locking in C++: CS 371P Object-Oriented Programming Lecture-10.fm
Synchronization Via Locking in C++: CS 371P Object-Oriented Programming Lecture-10.fm
fm
The basic technique is to ensure that the lock is set before a shared variable/object/method is accessed and that the
lock is reset when access is complete. In general, a lock should be held no longer than necessary. A very common bug
in concurrent programming is to forget to release the lock in some thread, in which case all threads waiting for the
lock cannot progress! For example:
m.lock();
x = x + 1; // access shared variabl
if (x < 5) {
...
}
else
return; // THIS CAUSES A PROBLEM, WHY!?
...
m.unlock();
}
Failure to release a lock usually occurs when there is more than one exit point from a procedure.
How might you implement a locking strategy in C++ so that the lock is set when a scope is entered, and unlocked
when the scope is exited, independent of the point at which you exit the scope?
void synchronized_procedure()
{
static mutex m; // why is this a static object?
Lock lock(m); // associate a lock with the entire block scope
In this case, the granularity of the lock is the entire scope of the synchronized_procedure. In C and C++, you can
define an arbitrary scope, and declare a Lock variable local to that scope to effect mutual exclusion for a single
statement or a group of statements. E.g.,
class Lock {
mutex& m;
public:
Lock(mutex& x) : m(x) { m.lock(); }
~Lock() { m.unlock(); }
};
The constructor grabs the lock and the destructor releases the lock, thereby guaranteeing that the lock is always
released when the scope in which a Lock object is declared is exited. This is really pretty neat, and ensures that you
never forget to release the lock.
The only problem is that you might not want to always set the lock at the start of the scope and release it at the end.
You might want to have a finer granularity of locking. In this case, you need to just use the lock directly, always
remembering to unlock it as soon as you are done accessing the shared information. You could also allocate a lock
object from the heap, as long as you remember to delete it at the right time. To complicate matters, if exception
handling is being used in a multi-threaded program, and there is some procedure that set a lock, but does not catch
the exceptions, then the lock will not be released when the stack is unwound as part of throwing an exception. So, the
safest thing to do in C++ is to use the Lock object as a stack allocated object associated with some scope.
Question: What happens to the lock in the following situation, when cond is true?
for (;;) {
static mutex m;
Lock lock(m);
if (cond)
break;
...
}
To make life easy ( depending on how you look at it), Java provides the synchronized modifier as the mechanism the
programmer uses to indicate that the lock for the object should be set when a method is called by a thread, and
implicitly released when the method returns (normally or abnormally). This way, the programmer is protected from
forgetting to release the lock. Forgetting to release a lock is a common bug in multi-threaded programming.
A simple example:
class BankAccount {
private double balance;
When a method executing in thread 1 calls withdrawal, the object is locked, and no other thread can execute the
withdrawal or deposit methods. Other threads can however call the method, but the Java virtual machine will make
sure the thread is blocked from executing the method until the object is unlocked.
If an exception is thrown from a synchronized method, then the object lock is implicitly released.
class BankAccount {
private double balance;
The synchronzied statement takes a non-null object reference as an argument, and locks the object if the object is not
already locked. What do you think happens in the following case, called recursive locking?
class Test {
public static void main(String[] args) {
Test t = new Test();
synchronized(t) {
synchronized(t) {
System.out.println(“This is a test”);
}}
}
}
package ReaderWriter;
import java.net.*;
import java.io.*;
public class Client {
Socket socket;
private Thread reader, writer;
// main just creates the client object, which starts the reader/writer threads
public static void main(String[] args) {
try { new Client(args[0], Integer.parseInt(args[1])); }
catch(NumberFormatException e) { System.err.println(e); System.exit(-1); }
}
}
public Reader(Client c) {
super(“Client Reader”); // pass identifying string to Thread(String) constructor
this.client = c;
}
public Writer(Client c) {
super(“Client Writer”);
this.client = c;
}