Process and Thread Synchronization
  1. Inter-process Communication
    1. Actually, inter-thread communications. May be in the same process or different processes.
    2. Use the kernel to send a message when threads are in different processes.
    3. Shared data structure.
      1. Shared in memory, updated by stores and fetches.
        1. Threads in the same process.
        2. Processes may request a shared memory region.
      2. Shared structure on disk manipulated by reads and writes.
        Local database.
      3. Shared structure in some remote server manipulated by network communications.
        1. Database controlled by a server.
        2. Shared file store.
  2. Race condition
    1. Shared data structures can be easily corrupted.
      void incr(int *loc) { int ret = *loc; ++ret; *loc = ret; }
    2. Note: Any increment is like this at the register level.
    3. Two threads operating in the wrong order can lose an increment.
  3. Dealing With Race Conditions.
    1. Eliminate the sharing, if possible.
    2. Mutual exclusion.
      1. The section of code that updates the shared data structure is a critical section or critical region.
      2. At most one thread is allowed to be executing it at any one time. That is mutual exclusion.
      3. Prevents a race condition.
    3. Implementing mutual exclusion
      1. Using software.
        1. Used ordinary programs; no OS support needed.
        2. Uses busy-waits to create delays.
        3. Lock Variable: Doesn't always work.
        4. Take Turns: Strict alternation.
        5. Peterson's: Works. Can be generalized to more than two.
        6. Not easy. Not efficient.
      2. Disable interrupts.
        1. Prevents change of process.
        2. Must not keep them off long.
        3. Can only be used by O/S. Traditionally has been.
        4. Only works on a uni-processor.
      3. Test and Set Lock
        1. Hardware instruction to support synchronization.
        2. Atomic version of
          bool testset(int *target) { bool ret = *target; *target = 1; return ret; }
        3. Using TSL: Fixes the lock variable by being atomic.
        4. Alternative to TSL: XCHG. Swap atomically.
        5. Still requires spin-locks.
    4. OS-Provided Synchronization.
      1. Synchronization primitives provided by the OS for use by programs.
      2. Able to suspend the program when it is waiting.
      3. Sleep/wakeup
        1. Sleep puts caller to sleep.
        2. Wakeup takes the id of a sleeping process and wakes it up.
        3. Alternatively, both sleep and wakup take an address which relate them. OS might use this as the location of a wait queue.
      4. Semaphore. An integer value with extra operations.
        1. Semaphore operations.
          1. Initialize to a non-negative value.
          2. down: Decrement; if result is negative, the caller blocks.
          3. up: Increment; if the result is non-positive, a waiting process is unblocked.
        2. Mutual exclusion
          semaphore s = 1; . . . down(s); /* Critical Section */ up(s);
        3. Producer-Consumer: One thread produces messages, while another consumes them.
      5. OS Support.
        1. Traditional Unix: semget, semop
        2. Win32: CreateSemaphore, WaitForSingleObject (used for many kinds of waits), ReleaseSemaphore
        3. Java Semaphore class.
        4. Neither pthreads nor the new C++ standard support semaphores. The mutex and condition variable abstractions which they do support can be used to build one without great trouble.
      6. Mutexes.
        1. A simplified version of a semaphore, without counting.
        2. A mutex may be locked.
          1. Threads support lock and unlock operations.
          2. Only one thread at a time may have the mutex locked.
          3. A thread executing lock on a locked mutex waits.
      7. OS Support
        1. Win32: CreateMutex, ReleaseMutex and the wait functions mentioned above.
        2. pthread_mutex_init, pthread_mutex_lock.
        3. C++ 2011 mutex
      8. Futex. A fast way to implement mutexes.
        1. This technique has been developed on Linux systems, but is not limited to Linux or Unix.
        2. Stay out of the kernel.
          1. Busy-waits are expensive, so it has been usual to implement synchronization operations in the kernel. Requires context swap.
          2. Futex tries to avoid entering the kernel unless a wait is required.
          3. Very fast under low conetention.
        3. Mechanism.
          1. The lock itself is a shared data structure protected using a a hardware atomic instruction, such at TSL or EXCHG.
          2. Use software/TSL to atomically check if the lock is locked, and ask the kernel to suspend (yourself) if so.
          3. On leaving a CS, use software/TSL to atomically check for waiters and tell the kernel to start one if so.
        4. If contention is low, the kernel need not be involved. If high, the kernel is used to avoid busy-waits.
      9. Monitors.
        1. A class which allows only one thread to be running in any method at any time.
        2. Special operations
          1. Mutual exclusion of the monitor (object) data since only one thread may be running a method at any time.
          2. Condition variables support the wait and signal operations.
          3. Wait operation makes caller wait unconditionally.
          4. Signal resumes one waiting thread, if any, otherwise no-op.
        3. Producer-Consumer using monitors.
        4. Note differences from the textbook version. It uses if's to avoid signals that are no-ops anyway.
        5. Practicalities
          1. Either signal is the last operation in the method, or it must mean the caller is suspended in favor of any signaled process.
          2. Practical implementation may allow a delay between signal and actual start, so process should recheck the condition after a wait.
          3. Practical implementations often provide a broadcast operation which starts all (possibly zero) waiting threads.
        6. OS Support
          1. Library-based systems (pthreads, Win32 calls) have difficulty implementing monitors: Requires language syntax.
          2. Java provides the synchronized keyword. A class which extends Thread and uses all synchronized methods has monitor-like mutual exclusion.
          3. Methods which are not marked synchronized have not special mutual exclusion semantics.
          4. Constructors cannot be synchronized; an object should be created by a single thread before being used.
          5. Java doesn't seem to have exactly a monitor condition variable, but a wide selection of synchronization objects in the java.util.concurrent package.
      10. Semaphore v. Monitor.
        1. Syntax reflects the protected data.
        2. Waits are unconditional; semaphore downs do not always wait.
        3. Signals are complete no-ops if there is no waiting; up changes the semaphore's value.
      11. Condition variables. Threading libraries often provide condition variables, which are sort of like monitor condition variables. C++-11:
        // Condition variables use a mutex, which stands in for the // mutual exclusion provided by a monitor. mutex mux; condition_variable available; ... // To wait for the resource to be available unique_lock<mutex> locker(mux); // Locks the mutex. available.wait(locker); // Unlocks the mutex, then the caller sleeps until awoken. // Before being awoken, the mutex is locked again. ... // To indicate that the resource is available, another thread does: available.notify_one(); // or available.notify_all();
      12. Message Passing.
        1. Message passing will have producer/consumer semantics, so it synchronizes the communicating threads.
        2. Unlike other methods, suitable for processors without shared memory.
        3. send(destination, message)
        4. receive(source, message)
        5. Receiver must wait until a message must be sent.
          May just return an error instead.
        6. Sending may be buffered, but the sender will have to wait for space if the buffer is full.
        7. Without buffers, send and receive much each wait for the other, so both are executed at the same time. Called a rendezvous.
      13. Barriers. Everyone waits until all reach the barrier.
      14. O/S support.
        1. Pthread: pthread_barrier_init, pthread_barrier_wait.
        2. Win32: Synchronization barriers
        3. Java: CyclicBarrier
        4. C++ doesn't seem to have one, though it wouldn't be too hard to implement with a small class containing a condition variable.
    5. Read-Copy-Update
      1. Avoid the need for synchronization by keeping any thread from seeing any intermediate version.
      2. Create and initialize the new node. Structure not yet updated.
      3. Change the A to X link in a single assignment. All threads see either old or new version.
  4. Classical Synchronization Problems.
    1. Dining philosophers
      1. An academic problem, proposed by Dijkstra.
      2. Five philosophers sit around a table.
      3. They each need two forks to eat, but there are only five between them.
      4. Each philosopher alternates eating and thinking.
      5. The problem is to make sure each philosopher has a fair chance to eat, and none starves. (Deadlock would be a failure.)
      6. Non-Solution
      7. Poor Solution
      8. Good Solution I
      9. Good Solution II
    2. Readers Writers
      1. Practical problem in database, etc.
      2. Shared data structure may be read by any number of threads.
      3. May be written by only one thread.
      4. May not be read and written at the same time.
      5. Readers and Writers.
      6. Writers can starve.
      7. Implementation in C++
        1. readerwriter.h.
        2. Test Application.
        3. semaphore.h and semaphore.cpp.
        4. Package Download.
  5. Thread primitives.
    1. Pthreads.
      1. Some pthreads primitives: pthread_create, pthread_join, pthread_mutex_init, pthread_mutex_lock, pthread_cond_init, pthread_cond_wait, pthread_cond_signal
      2. Pthread Readers/Writers Example
    2. C++
      1. thread, mutex and several others
      2. Synchronization Failure Demo
    3. Win32 Threads _beginthread, WaitForSingleObject handles pretty much every kind of wait, including mutex and event waits, and semaphore down, CreateSemaphore (with slightly peculiar semantics), ReleaseSemaphore is the up, CreateMutex, ReleaseMutex, CreateEvent, CreateEvent, SetEvent, ResetEvent
    4. In all of the above, the thread starting call takes the name of a function, which it starts running as a new thread, then returns immediately.
    5. Java.
    6. In Java, you must write your thread function to implement an abstract method in a class either extending Thread or implementing Runnable. You then create the object, and call a method to start your thread running.