Principles of Computer Systems
Autumn 2019
Stanford University
Computer Science Department
Lecturer: Chris Gregg
Philip Levis
mutex
: mutual exclusion (lock), used to enforce critical sections and atomicitycondition_variable
: way for threads to coordinate and signal when a variable has changed (integrates a lock for the variable)semaphore
: a generalization of a lock, where there can be n threads operating in parallel (a lock is a semaphore with n=1)mutex m;
void function(mutex &m) {
lock_guard<mutex> lg(m); // m is now locked
while (true) {
if (condition1) return; // lg automatically unlocked on return
// ...
if (condition2) break;
}
// mutex will be unlocked after this line when lg goes out of scope
}
conditional_variable
enables one thread to signal to other threads that a variable has changed (e.g., a work queue), such that a condition has changed (there's now work on the queue)static void waitForPermission(size_t& permits, condition_variable_any& cv, mutex& m) {
lock_guard<mutex> lg(m);
while (permits == 0) cv.wait(m);
permits--;
}
static void grantPermission(size_t& permits, condition_variable_any& cv, mutex& m) {
lock_guard<mutex> lg(m);
permits++;
if (permits == 1) cv.notify_all();
}
template <Predicate pred>
void condition_variable_any::wait(mutex& m, Pred pred) {
while (!pred()) wait(m);
}
static void waitForPermission(size_t& permits, condition_variable_any& cv, mutex& m) {
lock_guard<mutex> lg(m);
cv.wait(m, [&permits] { return permits > 0; });
permits--;
}
semaphore
class is not built in to C++, but it is a basic synchronization primitivesemaphore permits(5); // this will allow five permits
permits.wait(); // if five other threads currently hold permits, this will block
// only five threads can be here at once
permits.signal(); // if other threads are waiting, a permit will be available
const size_t BUF_SIZE = 8;
const size_t DATA_SIZE = 320; // 40 cycles around buffer
static char buffer[BUF_SIZE];
static void writer(char buffer[]) {
for (size_t i = 0; i < DATA_SIZE; i++) {
buffer[i % BUF_SIZE] = prepareData();
}
}
static void reader(char buffer[]) {
for (size_t i = 0; i < DATA_SIZE; i++) {
processData(buffer[i % BUF_SIZE]);
}
}
int main(int argc, const char *argv[]) {
thread w(writer, buffer);
thread r(reader, buffer);
w.join();
r.join();
return 0;
}
struct safe_queue {
char buffer[BUF_SIZE];
size_t head;
size_t tail;
mutex lock;
condition_variable_any cond;
};
int main(int argc, const char *argv[]) {
safe_queue queue;
thread w(writer, ref(queue));
thread r(reader, ref(queue);
w.join();
r.join();
return 0;
}
a | b | c |
---|
head
tail
head
tail
g | h | i | j | k | d | e | f |
---|
tail
head
ready
empty
full
static void writer(safe_queue& queue) {
for (size_t i = 0; i < DATA_SIZE; i++) {
queue.lock.lock();
while (full(queue)) {
cout << "Full" << endl;
queue.cond.wait(queue.lock);
}
queue.buffer[queue.tail] = prepareData();
queue.tail = (queue.tail + 1) % BUF_SIZE;
queue.lock.unlock();
queue.cond.notify_all();
}
}
static void reader(safe_queue& queue) {
for (size_t i = 0; i < DATA_SIZE; i++) {
queue.lock.lock();
while (empty(queue)) {
cout << "Empty" << endl;
queue.cond.wait(queue.lock);
}
processData(queue.buffer[queue.head]);
queue.head = (queue.head + 1) % BUF_SIZE;
queue.lock.unlock();
queue.cond.notify_all();
}
}
std::vector
std::map
int vector_print_thread(std::vector<int>& ints) {
size_t size = ints.size();
for (size_t i = 0; i < size; i++) {
std::cout << ints[i] << std::endl;
}
}
iterator
vector
iterator
vector
shared_mutex root_lock;
node* root;
node* left;
node* right;
unsigned int key;
void* data;
shared_mutex rwlock;
insert(unsigned int key, void* data)
insert_at(node* node, unsigned int key, void* data)
lock root pointer lock
if root pointer is null:
insert at root
unlock root pointer lock
return
else:
lock root node
unlock root pointer
insert_at(root_node)
if (key < node.key):
if node.left == NULL:
insert at node.left
unlock node
else:
lock node.left
unlock node
insert_at(node.left)
else:
if node.right == NULL:
insert at node.right
unlock node
else:
lock node.right
unlock node
insert_at(node.right)
Suppose we want to insert at the green point
Suppose we want to insert at the green point
lock root pointer lock
if root pointer is null:
insert at root
unlock root pointer lock
return
else:
lock root node
unlock root pointer
insert_at(root_node)
Suppose we want to insert at the green point
lock root pointer lock
if root pointer is null:
insert at root
unlock root pointer lock
return
else:
lock root node
unlock root pointer
insert_at(root_node)
Suppose we want to insert at the green point
lock root pointer lock
if root pointer is null:
insert at root
unlock root pointer lock
return
else:
lock root node
unlock root pointer
insert_at(root_node)
Suppose we want to insert at the green point
if (key < node.key): if node.left == NULL: insert at node.left unlock node else: lock node.left unlock node insert_at(node.left) else: if node.right == NULL: insert at node.right unlock node else: lock node.right unlock node insert_at(node.right)
Suppose we want to insert at the green point
if (key < node.key): if node.left == NULL: insert at node.left unlock node else: lock node.left unlock node insert_at(node.left) else: if node.right == NULL: insert at node.right unlock node else: lock node.right unlock node insert_at(node.right)
Suppose we want to insert at the green point
if (key < node.key): if node.left == NULL: insert at node.left unlock node else: lock node.left unlock node insert_at(node.left) else: if node.right == NULL: insert at node.right unlock node else: lock node.right unlock node insert_at(node.right)
Suppose we want to insert at the green point
if (key < node.key): if node.left == NULL: insert at node.left unlock node else: lock node.left unlock node insert_at(node.left) else: if node.right == NULL: insert at node.right unlock node else: lock node.right unlock node insert_at(node.right)
Suppose we want to insert at the green point
if (key < node.key): if node.left == NULL: insert at node.left unlock node else: lock node.left unlock node insert_at(node.left) else: if node.right == NULL: insert at node.right unlock node else: lock node.right unlock node insert_at(node.right)
Suppose we want to insert at the green point
if (key < node.key): if node.left == NULL: insert at node.left unlock node else: lock node.left unlock node insert_at(node.left) else: if node.right == NULL: insert at node.right unlock node else: lock node.right unlock node insert_at(node.right)
Deletion is much harder...
Three cases (two easy)
Node to delete is leaf: delete it, set parent pointer to null
Node to delete has one child: delete it, set parent pointer to its child
Node to delete has two children: find successor leaf, replace with this node
Case 1
Case 2
Case 3
Node to delete
Need to hold 4 locks!
Parent of node to be deleted
Node to be deleted
Parent of node to be moved
Node to be moved
Case 3
Need to hold 4 locks!
Parent of node to be deleted
Node to be deleted
Parent of node to be moved
Node to be moved
Case 3
find node to delete, hold lock and parent lock find successor of node, hold lock and parent lock change node's parent to point to successor change successor to point to node's children release node parent lock release node lock release successor parent lock release successor lock
Coarse
Fine