Journal 28
July 30 - August 5
Okay, I know I say this every week but wow, week six is over already! While I have thoroughly enjoyed my courses so far at CSUMB, I am so ready for a (albeit short) break between courses. The universe has blessed me with a very convenient timeline as this course ends on August 19th and I head out for Washington D.C. on August 21st for the WPBL tryouts! I was so worried that the two would overlap and I would be overwhelmed with stress and homework. This also serves as a huge motivating factor for me to keep pushing and finish strong! Anyways, here's the summary for this week:
Chapter 30 delved into condition variables. For example, pthread_cond_t is used to signal and wait on certain conditions. Typically, one thread is tasked with waiting such as pthread_cond_wait while another thread signals once the condition changes such as pthread_cond_signal. Wait() automatically releases the lock, puts the thread to sleep, and reacquires the lock upon waking. The text gives us the pattern:
pthread_mutex_lock(&lock);
pthread_mutex_lock(&lock);
while (!condition) {
pthread_cond_wait(&cond, &lock);
}
pthread_mutex_unlock(&lock);
and reminds us that signaling is merely a "hint" that the condition may be true, so we should always recheck the condition after waking. Also, it stresses the importance of using while rather than if before the wait() as the condition may no longer be true when the thread wakes up. The reading also explores other common errors such as not holding the lock when calling wait() or signal(), missing a condition variable entirely and spinning instead.
Chapter 31 explored semaphores. For example, sem_t has an internal counter, so sem_wait() decrements, and blocks is the counter is less than 0, and sem_post() increments, and wakes one waiting thread (if any). A binary semaphore has an initial value of 1 and acts like a lock. A counting semaphore has initial value of N and is used for managing multiple resources. Semaphores are commonly used in cases such as mutual exclusion (sem_wait() before critical section and sem_post() after), thread ordering (parent waits for child via semaphore initialized to 0), and producer/ consumer problems (3 semaphores: empty, full, and mutex). Deadlock can be avoided through acquisition of semaphores in a consistent global order.
Chapter 32 spoke of common concurrency problems such as atomicity violations, order violations, and deadlocks. Atomicity violations often occur when a code assumes a group of operations is atomic when it is not. To fix this issue, use locks to protect the sequence. An example is as follows:
if (x != NULL) do_something(x);
if (x != NULL) do_something(x);
// x may be set to NULL between check and use, therefore creating a bug in the functioning
Order violations occur when code assumes one event occurs before another, and the scheduler may disagree with this ordering. To rectify this issue, use condition variables or semaphores to enforce ordering. For example, thread A may be mThread = createThread(); and thread B may be use mThread; which may run before mThread is initialized.
Conditions for deadlocks include: mutual exclusion, hold and wait, no preemption, and circular wait. To avoid deadlocks, some techniques include: lock ordering (total or partial), try-locks with backoff, deadlock detection, and lock-free structures (though this one is considered advanced). The text provides us with the tip that we should always enforce a consistent lock acquisition order and avoid hidden dependencies across modules or function calls.
The Anderson/Dahlin method proposes that in order to design a correct concurrent program, one should adhere to the following: identification of shared resources, identification of invariants, identification of atomic operations, use of synchronization to enforce atomicity and preserve invariants, and use of a methodical approach to avoid deadlocks. This design philosophy encourages foresight about structure and correctness pf concurrent code prior to implementation.
Comments
Post a Comment