Common Concurrency Problem
2018-07-16
Non-Deadlock Bugs
Atomicity-Violation Bugs
- Definition of Atomicity Violation: The desired Serializability among multiple memory accesses is violated.
- Solution to this bug: Just add Locks around the shared-variable references.
1 | // Thread 1:: |
- 在这个例子中两个线程同时访问数据
thd
的proc_info
值,第一个线程检查该值是否为NULL
,如果不是则输出proc_info
的值,第二个线程将proc_info
的值改变为NULL
。 - 当线程一完成检测并确定
proc_info
的值不为NULL
时,线程二突然打断线程一并开始执行。
Order-Violation Bugs
- Definition of Order Violation: The desired order between two memory access is flipped
- So
1 | // Thread 1:: |
Deadlock Bugs
- Four conditions need to hold for a deadlock to ocur:
- Mutual Exclusion: Threads claim exclusive control of resources that they require
- Hold-and-wait: Thread hold resources allocated to them while waiting for additional resources
- No preemption: Resource cannot be forcibly removed from threads that are holding them
- Circular wait: There exists a circular chain of threads such that each thread holds one more resources that are being requested by the next thread.
Prevent Circular Wait
- To prevent Circular Wait, We must provide a Total Ordering on lock acquistion
Prevent Hold-and-Wait
- The hold-and-wait Requirement for deadlock can be avoided by acquiring all locks at once, atomically.
1
2
3
4
5lock(prevention);
lock(L1);
lock(L2);
// ...
unlock(prevention);
Prevent No Preemption
- Multiple lock acquisstion often gets us into trouble because when waiting for one lock we are holding another.
The following method helps to avoid this situation. The
trylock()
routine will grab the lock if it is available, or return -1 indicating that the lock is held right now.1
2
3
4
5
6top:
lock(L1);
if (trylock(L2) == -1) {
unlock(L1);
goto top;
}Livelock: It is possible that two threads could both be repeatedly attempting this sequence and repeatedly failing to acquire both locks. One could add a random delay before looping back and trying the entire thing over again.
Prevent Mutual Exclusion
- Using more powerful hardware instructions, you can build data structures in a manner that does not require explicit locking.
Assume we have a compare-and-swap instruction, which as you may recall is an atomic instruction provided by the hardware that does the following:
1
2
3
4
5
6
7int CompareAndSwap(int *address, int expected, int new) {
if (*address == expected) {
*address = nwe;
return 1;
}
return 0;
}Imagine we now want to atomically increment a value by a certain amount
1
2
3
4
5void AtomicIncrement(int *value, int amount) {
do {
int old = *value;
} while (CompareAndSwap(value, old, old + amount) == 0);
}Instead of acquiring a lock, doing the update, and then releasing it, we have instead built an approach that repeatedly tries to update the value to the new amout and uses the compare-and-swap to do so.
Prevent Deadlock via Scheduling
- This stratey requires some global knowledge of which locks various threads might grab during their execution, and subsequently schedules said threads in a way as to guarantee no deadlock can occur.
- In a specific scenario, we have two processors and four threads:
- Thread 1 grabs Lock 1 and Lock 2
- Thread 2 grabs Lock 1 and Lock 2
- Thread 3 just grabs Lock 2
- Thread 4 grabs no Locks.
- A smart scheduler could thus compute that as long as T1 and T2 are not run at the same time, no deadlock could ever arise.
- A deadlock detector runs periodically, building a resource graph and checking for cycyles. If a deadlock is detected, the system needs to be restarted.