pete > courses > CS 315 Fall 26 > lecture 14: threads and mutexes


Lecture 14: threads and mutexes

Goals


first, a lot of history

the view of the computer processor presented in Computer Architecture is that we’ve got an ALU, a register file, and memory

every time we tick the clock, a new instruction is executed, which causes things like data copied from memory to the register file or values in the register file passing through the ALU with the result stored back in the register file or data copied from the register file to memory

this is accurate, but it is (unsurprisingly) simplified


TODO: clockspeed increases, multi-processor systems, cores, multi-core systems, multithreading

all of that is not directly germaine to what you need to know in this course, it just sets the stage for why breaking up our tasks into multiple processes (or, shortly, multiple threads) helps us make more efficiency use of the hardware being produced today


we have seen how fork and exec can be used to implement concurrent execution: that is, multiple processes running at once

and, while those processes inherit certain characteristics of their parent, they do not share them

that is, a child process inherits all the variables and their values from the parent process, but changes to the variables in the child will not affect the variables in the parent

can we quantify this? can we figure out exactly what is shared between parent and child?

would I ask this question if the answer was "no" ?

fork-test.c

result: only memory shared past fork is that which was mmap’d with MAP_SHARED


so only memory explicitly mapped as shared will be… you know, shared between parent and child; all other memory is completely segregated

but what if we want the two processes to work together? it would help if they could share data/state more conveniently

enter: threads

sometimes called lightweight processes (LWP)

they’re a lot like processes, except they can be made to "share" more state (eg, pid, specific parts of memory)

the kernel is responsible for scheduling threads, however, just as it is responsible for scheduling processes

(interestingly, this wasn’t always the case: the library we’ll be using for this stuff used to be entirely userspace, and there still remain threading libraries that exist entirely in userspace)


the pthread ("POSIX threads") library provides both threading and synchronization functionality

need to #include <pthread.h> and link with -lpthread


pthread_create is the threading analog to fork: it’s how we create a new thread

the third parameter is the important one: it’s the function that should execute within the new thread; when this function returns, the thread is finished

thread1.c

wildly unpredictable, in a bad way

sometimes the child prints, sometimes it doesn’t

it we run it twice, sometimes the child of the first process prints when the second process runs

I honestly have no idea what’s going on here, but it’s not good


we can fix this by introducing the threading equivalent of wait, which is pthread_join

as demonstrated here

thread2.c

now the child always prints before the parent exits

note that the pids are a) the same and b) increase by 2


I’m going to introduce a delay before the child (and therefore, by extension, the parent) exits

so we can examine the list of running processes while both threads are active

thread3.c

ps auxww | grep thread3 shows a single process

(in previous semesters, /proc/<pid>/maps showed a separate stack for the child thread)


recall the fork-based program I showed a few weeks ago that demonstrated child processes interleaving their output (see lecture 11)

I wrote the equivalent program using threads

thread-interleaving.c

it has some interesting peculiarities

note how we pass data to a thread (must be a void *)

note that we must have the pthread_t identifying the thread to join (ie, wait for) it

note that the threads are round-robin scheduled!


now to see how the same test works with threads

thread-test.c

all memory is shared past pthread_create

good to know


now, finally, to demonstrate critical sections and mutual exclusion

I’ve slightly modified the interleaving code from before with the idea that each thread’s critical section is where it causes output

so where we previously saw all that interleaving, we would now like to see a solid chunk of zeroes, a solid chunk of ones, and a solid chunk of twos (though we may see all the twos first and then all the ones, etc)

critical-section-bad.c

clearly this does not happen just by wishing


here’s the same program with mutexes

critical-section-mutex.c

really simple: acquire the mutex before the critical section, release it afterwards

Last modified: