Thursday: hw4 (C++ / boost strings) & threads
Friday: Eraser / data race detection
next Monday: undefined behavior
next Friday: wrapup
review sockets & concurrency
synchronization: bugs & programming interface
AF_INET[6]
, sockaddr_in[6]
extend echo server (echosrv.c)
print out client/server IP address & port: echosrv2.c
virtualization: process & thread
separate vs shared address space
review CSE 351: processes
#include <iostream>
#include <vector>
long psum(long *v, size_t offset, size_t n) {
long result = 0;
for (size_t i = 0; i < n; ++i) result += v[offset + i];
return result;
}
int main() {
std::vector<long> v(10000, 1);
std::cout << "result: " << psum(&v[0], 0, v.size()) << std::endl;
}
modify the program to use two threads: psum.cc
fork
? pthread_create
? std::thread
?
see also: ex17, openmp
atomicity: all-or-nothing
ordering: A must happen before B
progress: no starvation/deadlock
#include <iostream>
#include <thread>
#include <vector>
static int counter;
void foo() { ++counter; }
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 1000; ++i) threads.push_back(std::thread(foo));
for (auto &thr : threads) thr.join();
std::cout << counter << std::endl;
}
what’s the result?
data race - undefined!
concurrent access to shared data; at least one write
undefined behavior - illegal
compiler/CPU may reorder & change your code
example: two threads incrementing a shared counter
see also: Therac-25, Northeast blackout of 2003
#include <iostream>
#include <thread>
static int flag = 0;
void signal_proc() {
std::cout << "before signal" << std::endl;
flag = 1;
}
void wait_proc() {
while (flag == 0) {}
std::cout << "after wait" << std::endl;
}
int main() {
std::thread t1(signal_proc), t2(wait_proc);
t1.join(); t2.join();
}
undefined behavior: g++-4.9 -O2
, infinite loop
use pthreads/C/C++ primitives to avoid data races
locking: pthread_mutex_t, mutx_t, std::mutex
atomic: _Atomic, std::atomic
wait-and-signal: pthread_cond_t, cnd_t, std::condition_variable
demo: global counter
compare-and-swap instruction: cmpxchg
/* PSEUDO CODE */
int cmpxchg(int *addr, int old, int new) {
int was = *addr;
if (was == old)
*addr = new;
return was;
}
name | balance |
---|---|
Alice | 230 |
Bob | 120 |
… | … |
bank: multithreaded server, hashtable (name, balance)
transfer money from Alice to Bob
invariants?
coarse-grained locking: global interpreter lock
fine-grained locking
read-write lock: <shared_mutex>
, read-copy-update, …
…
trade-off: performance vs correctness
lock 1 - Alice, lock 2 - Bob
thread 1: grabbed lock 1, acquiring lock 2
thread 2: grabbed lock 2, acquiring lock 1
prevention: order locks
example: std::lock
hard to reproduce: non-deterministic
hard to understand: large number of interleavings
next lecture: tools & debugging techniques