Threads / Synchronization 1 1 Changelog Changes made in this - - PowerPoint PPT Presentation

threads synchronization 1
SMART_READER_LITE
LIVE PREVIEW

Threads / Synchronization 1 1 Changelog Changes made in this - - PowerPoint PPT Presentation

Threads / Synchronization 1 1 Changelog Changes made in this version not seen in fjrst lecture: 22 September 2019: thread resources exercise (whats wrong with this): be more consistent about thread function name 30 September 2019: passing


slide-1
SLIDE 1

Threads / Synchronization 1

1

slide-2
SLIDE 2

Changelog

Changes made in this version not seen in fjrst lecture:

22 September 2019: thread resources exercise (what’s wrong with this): be more consistent about thread function name 30 September 2019: passing data structures: include mandatory return NULL on thread function

1

slide-3
SLIDE 3

last time

fair schedulers and proportional share

lottery — random choice CFS — equalize ‘virtual times’

tradeofg: credit for time while not runnable real-time schedulers

2

slide-4
SLIDE 4

lottery scheduler assignment

track “ticks” process runs

= number of times scheduled simplifjcation: don’t care if process uses less than timeslice

new system call: getprocesesinfo

copy info from process table into user space

new system call: settickets

set number of tickets for current process should be inherited by fork

scheduler: choose pseudorandom weighted by tickets

caution! no fmoating point

3

slide-5
SLIDE 5

thread versus process state

thread state — kept in thread control block

registers (including program counter)

  • ther information?

process state — kept in process control block

address space (memory layout)

  • pen fjles

process id …

4

slide-6
SLIDE 6

Linux idea: task_struct

Linux model: single “task” structure = thread pointers to address space, open fjle list, etc. pointers can be shared — if same process fork()-like system call “clone”: choose what to share

clone(CLONE_FILES, ...) — new process sharing open fjles clone(CLONE_VM, ...) — new process sharing address spaces

advantage: no special logic for threads (mostly)

5

slide-7
SLIDE 7

Linux idea: task_struct

Linux model: single “task” structure = thread pointers to address space, open fjle list, etc. pointers can be shared — if same process fork()-like system call “clone”: choose what to share

clone(CLONE_FILES, ...) — new process sharing open fjles clone(CLONE_VM, ...) — new process sharing address spaces

advantage: no special logic for threads (mostly)

5

slide-8
SLIDE 8

aside: alternate threading models

we’ll talk about kernel threads OS scheduler deals directly with threads alternate idea: library code handles threading kernel doesn’t know about threads w/in process hierarchy of schedulers: one for processes, one within each process not currently common model — awkward with multicore

6

slide-9
SLIDE 9

why threads?

concurrency: difgerent things happening at once

  • ne thread per user of web server?
  • ne thread per page in web browser?
  • ne thread to play audio, one to read keyboard, …?

parallelism: do same thing with more resources

multiple processors to speed-up simulation (life assignment)

7

slide-10
SLIDE 10

pthread_create

void *ComputePi(void *argument) { ... } void *PrintClassList(void *argument) { ... } int main() { pthread_t pi_thread, list_thread; pthread_create(&pi_thread, NULL, ComputePi, NULL); pthread_create(&list_thread, NULL, PrintClassList, NULL); ... /* more code */ }

run ComputePi and PrintClassList at the same time also run “more code”

thread identifjer — used to perform operations on thread later function to run — thread starts here, terminate if function returns thread attributes (extra settings) and function argument

8

slide-11
SLIDE 11

pthread_create

void *ComputePi(void *argument) { ... } void *PrintClassList(void *argument) { ... } int main() { pthread_t pi_thread, list_thread; pthread_create(&pi_thread, NULL, ComputePi, NULL); pthread_create(&list_thread, NULL, PrintClassList, NULL); ... /* more code */ }

run ComputePi and PrintClassList at the same time also run “more code”

thread identifjer — used to perform operations on thread later function to run — thread starts here, terminate if function returns thread attributes (extra settings) and function argument

8

slide-12
SLIDE 12

pthread_create

void *ComputePi(void *argument) { ... } void *PrintClassList(void *argument) { ... } int main() { pthread_t pi_thread, list_thread; pthread_create(&pi_thread, NULL, ComputePi, NULL); pthread_create(&list_thread, NULL, PrintClassList, NULL); ... /* more code */ }

run ComputePi and PrintClassList at the same time also run “more code”

thread identifjer — used to perform operations on thread later function to run — thread starts here, terminate if function returns thread attributes (extra settings) and function argument

8

slide-13
SLIDE 13

pthread_create

void *ComputePi(void *argument) { ... } void *PrintClassList(void *argument) { ... } int main() { pthread_t pi_thread, list_thread; pthread_create(&pi_thread, NULL, ComputePi, NULL); pthread_create(&list_thread, NULL, PrintClassList, NULL); ... /* more code */ }

run ComputePi and PrintClassList at the same time also run “more code”

thread identifjer — used to perform operations on thread later function to run — thread starts here, terminate if function returns thread attributes (extra settings) and function argument

8

slide-14
SLIDE 14

a threading race

#include <pthread.h> #include <stdio.h> void *print_message(void *ignored_argument) { printf("In the thread\n"); return NULL; } int main() { printf("About to start thread\n"); pthread_t the_thread; pthread_create(&the_thread, NULL, print_message, NULL); printf("Done starting thread\n"); return 0; }

My machine: outputs In the thread about 4% of the time. What happened?

9

slide-15
SLIDE 15

a race

returning from main exits the entire process (all threads) race: main’s return 0 or print_message’s printf fjrst?

time

main: printf/pthread_create/printf/return print_message: printf/return

return from main ends all threads in the process

10

slide-16
SLIDE 16

fjxing the race (version 1)

#include <pthread.h> #include <stdio.h> void *print_message(void *ignored_argument) { printf("In the thread\n"); return NULL; } int main() { printf("About to start thread\n"); pthread_t the_thread; pthread_create(&the_thread, NULL, print_message, NULL); printf("Done starting thread\n"); pthread_join(the_thread, NULL); /* WAIT FOR THREAD */ return 0; }

11

slide-17
SLIDE 17

fjxing the race (version 2; not recommended)

#include <pthread.h> #include <stdio.h> void *print_message(void *ignored_argument) { printf("In the thread\n"); return NULL; } int main() { printf("About to start thread\n"); pthread_t the_thread; pthread_create(&the_thread, NULL, print_message, NULL); printf("Done starting thread\n"); pthread_exit(NULL); }

12

slide-18
SLIDE 18

pthread_join, pthread_exit

pthread_join: wait for thread, returns its return value

like waitpid, but for a thread return value is pointer to anything

pthread_exit: exit current thread, returning a value

like exit or returning from main, but for a single thread same efgect as returning from function passed to pthread_create

13

slide-19
SLIDE 19

passing thread IDs (1)

DataType items[1000]; void *thread_function(void *argument) { int thread_id = (int) argument; int start = 500 * thread_id; int end = start + 500; for (int i = start; i < end; ++i) { DoSomethingWith(items[i]); } ... } void run_threads() { vector<pthread_t> threads(2); for (int i = 0; i < 2; ++i) { pthread_create(&threads[i], NULL, thread_function, (void*) i); } }

14

slide-20
SLIDE 20

passing thread IDs (1)

DataType items[1000]; void *thread_function(void *argument) { int thread_id = (int) argument; int start = 500 * thread_id; int end = start + 500; for (int i = start; i < end; ++i) { DoSomethingWith(items[i]); } ... } void run_threads() { vector<pthread_t> threads(2); for (int i = 0; i < 2; ++i) { pthread_create(&threads[i], NULL, thread_function, (void*) i); } }

14

slide-21
SLIDE 21

passing thread IDs (2)

DataType items[1000]; int num_threads; void *thread_function(void *argument) { int thread_id = (int) argument; int start = thread_id * (1000 / num_threads); int end = start + (1000 / num_threads); if (thread_id == num_threads − 1) end = 1000; for (int i = start; i < end; ++i) { DoSomethingWith(items[i]); } ... } void run_threads() { vector<pthread_t> threads(num_threads); for (int i = 0; i < num_threads; ++i) { pthread_create(&threads[i], NULL, thread_function, (void*) i); } ... }

15

slide-22
SLIDE 22

passing thread IDs (2)

DataType items[1000]; int num_threads; void *thread_function(void *argument) { int thread_id = (int) argument; int start = thread_id * (1000 / num_threads); int end = start + (1000 / num_threads); if (thread_id == num_threads − 1) end = 1000; for (int i = start; i < end; ++i) { DoSomethingWith(items[i]); } ... } void run_threads() { vector<pthread_t> threads(num_threads); for (int i = 0; i < num_threads; ++i) { pthread_create(&threads[i], NULL, thread_function, (void*) i); } ... }

15

slide-23
SLIDE 23

passing data structures

class ThreadInfo { public: ... }; void *thread_function(void *argument) { ThreadInfo *info = (ThreadInfo *) argument; ... delete info; return NULL; } void run_threads(int N) { vector<pthread_t> threads(num_threads); for (int i = 0; i < num_threads; ++i) { pthread_create(&threads[i], NULL, thread_function, (void *) new ThreadInfo(...)); } ... }

16

slide-24
SLIDE 24

passing data structures

class ThreadInfo { public: ... }; void *thread_function(void *argument) { ThreadInfo *info = (ThreadInfo *) argument; ... delete info; return NULL; } void run_threads(int N) { vector<pthread_t> threads(num_threads); for (int i = 0; i < num_threads; ++i) { pthread_create(&threads[i], NULL, thread_function, (void *) new ThreadInfo(...)); } ... }

16

slide-25
SLIDE 25

what’s wrong with this?

/* omitted: headers, using statements */ void *create_string(void *ignored_argument) { string result; result = ComputeString(); return &result; } int main() { pthread_t the_thread; pthread_create(&the_thread, NULL, create_string, NULL); string *string_ptr; pthread_join(the_thread, &string_ptr); cout << "string is " << *string_ptr; }

17

slide-26
SLIDE 26

program memory

0xFFFF FFFF FFFF FFFF 0xFFFF 8000 0000 0000 0x7F… 0x0000 0000 0040 0000 Used by OS main thread stack second thread stack third thread stack Heap / other dynamic Code / Data dynamically allocated stacks string result allocated here string_ptr pointed to here …stacks deallocated when threads exit/are joined

18

slide-27
SLIDE 27

program memory

0xFFFF FFFF FFFF FFFF 0xFFFF 8000 0000 0000 0x7F… 0x0000 0000 0040 0000 Used by OS main thread stack second thread stack third thread stack Heap / other dynamic Code / Data dynamically allocated stacks string result allocated here string_ptr pointed to here …stacks deallocated when threads exit/are joined

18

slide-28
SLIDE 28

thread resources

to create a thread, allocate: new stack (how big???) thread control block pthreads: by default need to join thread to deallocate everything thread kept around to allow collecting return value

19

slide-29
SLIDE 29

pthread_detach

void *show_progress(void * ...) { ... } void spawn_show_progress_thread() { pthread_t show_progress_thread; pthread_create(&show_progress_thread, NULL, show_progress, NULL); pthread_detach(show_progress_thread); } int main() { spawn_show_progress_thread(); do_other_stuff(); ... }

detach = don’t care about return value, etc.system will deallocate when thread terminates

20

slide-30
SLIDE 30

starting threads detached

void *show_progress(void * ...) { ... } void spawn_show_progress_thread() { pthread_t show_progress_thread; pthread_attr_t attrs; pthread_attr_init(&attrs); pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED); pthread_create(&show_progress_thread, attrs, show_progress, NULL); pthread_attr_destroy(&attrs); }

21

slide-31
SLIDE 31

setting stack sizes

void *show_progress(void * ...) { ... } void spawn_show_progress_thread() { pthread_t show_progress_thread; pthread_attr_t attrs; pthread_attr_init(&attrs); pthread_attr_setstacksize(&attrs, 32 * 1024 /* bytes */); pthread_create(&show_progress_thread, attrs, show_progress, NULL); }

22

slide-32
SLIDE 32

sum example (to global)

int values[1024]; int results[2]; void *sum_thread(void *argument) { int id = (int) argument; int sum = 0; for (int i = id * 512; i < (id + 1) * 512; ++i) { sum += values[i]; } results[id] = sum; return NULL; } int sum_all() { pthread_t thread[2]; for (int i = 0; i < 2; ++i) { pthread_create(&threads[i], NULL, sum_thread, (void *) i); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return results[0] + results[1]; }

values, results: global variables — shared

23

slide-33
SLIDE 33

sum example (to global)

int values[1024]; int results[2]; void *sum_thread(void *argument) { int id = (int) argument; int sum = 0; for (int i = id * 512; i < (id + 1) * 512; ++i) { sum += values[i]; } results[id] = sum; return NULL; } int sum_all() { pthread_t thread[2]; for (int i = 0; i < 2; ++i) { pthread_create(&threads[i], NULL, sum_thread, (void *) i); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return results[0] + results[1]; }

values, results: global variables — shared

23

slide-34
SLIDE 34

sum example (to main stack, global values)

int values[1024]; struct ThreadInfo { int start, end, result; }; void *sum_thread(void *argument) { ThreadInfo *my_info = (ThreadInfo *) argument; int sum = 0; for (int i = my_info->start; i < my_info->end; ++i) { sum += values[i]; } my_info->result = sum; return NULL; } int sum_all() { pthread_t thread[2]; ThreadInfo info[2]; for (int i = 0; i < 2; ++i) { info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&threads[i], NULL, sum_thread, &info[i]); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return info[0].result + info[1].result; }

values: global variable — shared my_info: pointer to sum_all’s stack

  • nly okay because sum_all waits!

24

slide-35
SLIDE 35

sum example (to main stack, global values)

int values[1024]; struct ThreadInfo { int start, end, result; }; void *sum_thread(void *argument) { ThreadInfo *my_info = (ThreadInfo *) argument; int sum = 0; for (int i = my_info->start; i < my_info->end; ++i) { sum += values[i]; } my_info->result = sum; return NULL; } int sum_all() { pthread_t thread[2]; ThreadInfo info[2]; for (int i = 0; i < 2; ++i) { info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&threads[i], NULL, sum_thread, &info[i]); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return info[0].result + info[1].result; }

values: global variable — shared my_info: pointer to sum_all’s stack

  • nly okay because sum_all waits!

24

slide-36
SLIDE 36

sum example (to main stack, global values)

int values[1024]; struct ThreadInfo { int start, end, result; }; void *sum_thread(void *argument) { ThreadInfo *my_info = (ThreadInfo *) argument; int sum = 0; for (int i = my_info->start; i < my_info->end; ++i) { sum += values[i]; } my_info->result = sum; return NULL; } int sum_all() { pthread_t thread[2]; ThreadInfo info[2]; for (int i = 0; i < 2; ++i) { info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&threads[i], NULL, sum_thread, &info[i]); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return info[0].result + info[1].result; }

values: global variable — shared my_info: pointer to sum_all’s stack

  • nly okay because sum_all waits!

24

slide-37
SLIDE 37

sum example (to main stack, global values)

int values[1024]; struct ThreadInfo { int start, end, result; }; void *sum_thread(void *argument) { ThreadInfo *my_info = (ThreadInfo *) argument; int sum = 0; for (int i = my_info->start; i < my_info->end; ++i) { sum += values[i]; } my_info->result = sum; return NULL; } int sum_all() { pthread_t thread[2]; ThreadInfo info[2]; for (int i = 0; i < 2; ++i) { info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&threads[i], NULL, sum_thread, &info[i]); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return info[0].result + info[1].result; }

values: global variable — shared my_info: pointer to sum_all’s stack

  • nly okay because sum_all waits!

24

slide-38
SLIDE 38

program memory (to main stack, global values)

0xFFFF FFFF FFFF FFFF 0xFFFF 8000 0000 0000 0x7F… 0x0000 0000 0040 0000 Used by OS main thread stack info array second thread stack my_info third thread stack my_info Heap / other dynamic Code / Data values (global)

25

slide-39
SLIDE 39

sum example (to main stack)

struct ThreadInfo { int *values; int start; int end; int result }; void *sum_thread(void *argument) { ThreadInfo *my_info = (ThreadInfo *) argument; int sum = 0; for (int i = my_info->start; i < my_info->end; ++i) { sum += my_info->values[i]; } my_info->result = sum; return NULL; } int sum_all(int *values) { ThreadInfo info[2]; pthread_t thread[2]; for (int i = 0; i < 2; ++i) { info[i].values = values; info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&threads[i], NULL, sum_thread, (void *) &info[i]); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return info[0].result + info[1].result; }

26

slide-40
SLIDE 40

sum example (to main stack)

struct ThreadInfo { int *values; int start; int end; int result }; void *sum_thread(void *argument) { ThreadInfo *my_info = (ThreadInfo *) argument; int sum = 0; for (int i = my_info->start; i < my_info->end; ++i) { sum += my_info->values[i]; } my_info->result = sum; return NULL; } int sum_all(int *values) { ThreadInfo info[2]; pthread_t thread[2]; for (int i = 0; i < 2; ++i) { info[i].values = values; info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&threads[i], NULL, sum_thread, (void *) &info[i]); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return info[0].result + info[1].result; }

26

slide-41
SLIDE 41

sum example (to main stack)

struct ThreadInfo { int *values; int start; int end; int result }; void *sum_thread(void *argument) { ThreadInfo *my_info = (ThreadInfo *) argument; int sum = 0; for (int i = my_info->start; i < my_info->end; ++i) { sum += my_info->values[i]; } my_info->result = sum; return NULL; } int sum_all(int *values) { ThreadInfo info[2]; pthread_t thread[2]; for (int i = 0; i < 2; ++i) { info[i].values = values; info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&threads[i], NULL, sum_thread, (void *) &info[i]); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return info[0].result + info[1].result; }

26

slide-42
SLIDE 42

sum example (to main stack)

struct ThreadInfo { int *values; int start; int end; int result }; void *sum_thread(void *argument) { ThreadInfo *my_info = (ThreadInfo *) argument; int sum = 0; for (int i = my_info->start; i < my_info->end; ++i) { sum += my_info->values[i]; } my_info->result = sum; return NULL; } int sum_all(int *values) { ThreadInfo info[2]; pthread_t thread[2]; for (int i = 0; i < 2; ++i) { info[i].values = values; info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&threads[i], NULL, sum_thread, (void *) &info[i]); } for (int i = 0; i < 2; ++i) pthread_join(threads[i], NULL); return info[0].result + info[1].result; }

26

slide-43
SLIDE 43

program memory (to main stack)

0xFFFF FFFF FFFF FFFF 0xFFFF 8000 0000 0000 0x7F… 0x0000 0000 0040 0000 Used by OS main thread stack info array values (stack? heap?) second thread stack my_info third thread stack my_info Heap / other dynamic Code / Data

27

slide-44
SLIDE 44

sum example (on heap)

struct ThreadInfo { pthread_t thread; int *values; int start; int end; int result }; void *sum_thread(void *argument) { ... } ThreadInfo *start_sum_all(int *values) { ThreadInfo *info = new ThreadInfo[2]; for (int i = 0; i < 2; ++i) { info[i].values = values; info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&info[i].thread, NULL, sum_thread, (void *) &info[i]); } return info; } void finish_sum_all(ThreadInfo *info) { for (int i = 0; i < 2; ++i) pthread_join(info[i].thread, NULL); int result = info[0].result + info[1].result; delete[] info; return result; }

28

slide-45
SLIDE 45

sum example (on heap)

struct ThreadInfo { pthread_t thread; int *values; int start; int end; int result }; void *sum_thread(void *argument) { ... } ThreadInfo *start_sum_all(int *values) { ThreadInfo *info = new ThreadInfo[2]; for (int i = 0; i < 2; ++i) { info[i].values = values; info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&info[i].thread, NULL, sum_thread, (void *) &info[i]); } return info; } void finish_sum_all(ThreadInfo *info) { for (int i = 0; i < 2; ++i) pthread_join(info[i].thread, NULL); int result = info[0].result + info[1].result; delete[] info; return result; }

28

slide-46
SLIDE 46

sum example (on heap)

struct ThreadInfo { pthread_t thread; int *values; int start; int end; int result }; void *sum_thread(void *argument) { ... } ThreadInfo *start_sum_all(int *values) { ThreadInfo *info = new ThreadInfo[2]; for (int i = 0; i < 2; ++i) { info[i].values = values; info[i].start = i*512; info[i].end = (i+1)*512; pthread_create(&info[i].thread, NULL, sum_thread, (void *) &info[i]); } return info; } void finish_sum_all(ThreadInfo *info) { for (int i = 0; i < 2; ++i) pthread_join(info[i].thread, NULL); int result = info[0].result + info[1].result; delete[] info; return result; }

28

slide-47
SLIDE 47

program memory (on heap)

0xFFFF FFFF FFFF FFFF 0xFFFF 8000 0000 0000 0x7F… 0x0000 0000 0040 0000 Used by OS main thread stack second thread stack third thread stack Heap / other dynamic info array values (stack? heap?) my_info my_info Code / Data

29

slide-48
SLIDE 48

a note on error checking

from pthread_create manpage: special constants for return value same pattern for many other pthreads functions will often omit error checking in slides for brevity

30

slide-49
SLIDE 49

error checking pthread_create

int error = pthread_create(...); if (error != 0) { /* print some error message */ }

31

slide-50
SLIDE 50

the correctness problem

schedulers introduce non-determinism

scheduler might run threads in any order scheduler can switch threads at any time

worse with threads on multiple cores

cores not precisely synchronized (stalling for caches, etc., etc.) difgerent cores happen in difgerent order each time

makes reliable testing very diffjcult solution: correctness by design

32

slide-51
SLIDE 51

example application: ATM server

commands: withdraw, deposit

  • ne correctness goal: don’t lose money

33

slide-52
SLIDE 52

ATM server

(pseudocode) ServerLoop() { while (true) { ReceiveRequest(&operation, &accountNumber, &amount); if (operation == DEPOSIT) { Deposit(accountNumber, amount); } else ... } } Deposit(accountNumber, amount) { account = GetAccount(accountId); account−>balance += amount; StoreAccount(account); }

34

slide-53
SLIDE 53

a threaded server?

Deposit(accountNumber, amount) { account = GetAccount(accountId); account−>balance += amount; StoreAccount(account); }

maybe Get/StoreAccount can be slow?

read/write disk sometimes? contact another server sometimes?

maybe lots of requests to process?

maybe real logic has more checks than Deposit() …

all reasons to handle multiple requests at once → many threads all running the server loop

35

slide-54
SLIDE 54

multiple threads

main() { for (int i = 0; i < NumberOfThreads; ++i) { pthread_create(&server_loop_threads[i], NULL, ServerLoop, NULL); } ... } ServerLoop() { while (true) { ReceiveRequest(&operation, &accountNumber, &amount); if (operation == DEPOSIT) { Deposit(accountNumber, amount); } else ... } }

36

slide-55
SLIDE 55

a side note

why am I spending time justifying this? multiple threads for something like this make things much trickier we’ll be learning why…

37

slide-56
SLIDE 56

the lost write

account−>balance += amount; (in two threads, same account) mov account−>balance, %rax add amount, %rax

Thread A Thread B

mov account−>balance, %rax add amount, %rax mov %rax, account−>balance mov %rax, account−>balance context switch context switch context switch

lost write to balance “winner” of the race lost track of thread A’s money

38

slide-57
SLIDE 57

the lost write

account−>balance += amount; (in two threads, same account) mov account−>balance, %rax add amount, %rax

Thread A Thread B

mov account−>balance, %rax add amount, %rax mov %rax, account−>balance mov %rax, account−>balance context switch context switch context switch

lost write to balance “winner” of the race lost track of thread A’s money

38

slide-58
SLIDE 58

the lost write

account−>balance += amount; (in two threads, same account) mov account−>balance, %rax add amount, %rax

Thread A Thread B

mov account−>balance, %rax add amount, %rax mov %rax, account−>balance mov %rax, account−>balance context switch context switch context switch

lost write to balance “winner” of the race lost track of thread A’s money

38

slide-59
SLIDE 59

thinking about race conditions (1)

what are the possible values of x? (initially x = y = 0) Thread A Thread B x ← 1 y ← 2 must be 1. Thread B can’t do anything

39

slide-60
SLIDE 60

thinking about race conditions (1)

what are the possible values of x? (initially x = y = 0) Thread A Thread B x ← 1 y ← 2 must be 1. Thread B can’t do anything

39

slide-61
SLIDE 61

thinking about race conditions (2)

what are the possible values of x? (initially x = y = 0) Thread A Thread B x ← y + 1 y ← 2 y ← y × 2 1 or 3 or 5 (non-deterministic)

40

slide-62
SLIDE 62

thinking about race conditions (2)

what are the possible values of x? (initially x = y = 0) Thread A Thread B x ← y + 1 y ← 2 y ← y × 2 1 or 3 or 5 (non-deterministic)

40

slide-63
SLIDE 63

thinking about race conditions (3)

what are the possible values of x? (initially x = y = 0) Thread A Thread B x ← 1 x ← 2 1 or 2 …but why not 3? maybe each bit of assigned seperately?

41

slide-64
SLIDE 64

thinking about race conditions (3)

what are the possible values of x? (initially x = y = 0) Thread A Thread B x ← 1 x ← 2 1 or 2 …but why not 3? maybe each bit of assigned seperately?

41

slide-65
SLIDE 65

thinking about race conditions (3)

what are the possible values of x? (initially x = y = 0) Thread A Thread B x ← 1 x ← 2 1 or 2 …but why not 3? maybe each bit of x assigned seperately?

41

slide-66
SLIDE 66

atomic operation

atomic operation = operation that runs to completion or not at all we will use these to let threads work together most machines: loading/storing words is atomic

so can’t get 3 from x ← 1 and x ← 2 running in parallel

but some instructions are not atomic

  • ne example: normal x86 add constant to memory

42

slide-67
SLIDE 67

lost adds (program)

.global update_loop update_loop: addl $1, the_value // the_value (global variable) += 1 dec %rdi // argument 1 -= 1 jg update_loop // if argument 1 >= 0 repeat ret int the_value; extern void *update_loop(void *); int main(void) { the_value = 0; pthread_t A, B; pthread_create(&A, NULL, update_loop, (void*) 1000000); pthread_create(&B, NULL, update_loop, (void*) 1000000); pthread_join(A, NULL); pthread_join(B, NULL); // expected result: 1000000 + 1000000 = 2000000 printf("the_value = %d\n", the_value); }

43

slide-68
SLIDE 68

lost adds (results)

800000 1000000 1200000 1400000 1600000 1800000 2000000 1000 2000 3000 4000 5000 frequency

the_value = ? 44

slide-69
SLIDE 69

but how?

probably not possible on single core

exceptions can’t occur in the middle of add instruction

…but ‘add to memory’ implemented with multiple steps

still needs to load, add, store internally can be interleaved with what other cores do

(and actually it’s more complicated than that — we’ll talk later)

45

slide-70
SLIDE 70

but how?

probably not possible on single core

exceptions can’t occur in the middle of add instruction

…but ‘add to memory’ implemented with multiple steps

still needs to load, add, store internally can be interleaved with what other cores do

(and actually it’s more complicated than that — we’ll talk later)

45

slide-71
SLIDE 71

so, what is actually atomic

for now we’ll assume: load/stores of ‘words’

(64-bit machine = 64-bits words)

in general: processor designer will tell you their job to design caches, etc. to work as documented

46

slide-72
SLIDE 72

too much milk

roommates Alice and Bob want to keep fridge stocked with milk:

time Alice Bob 3:00 look in fridge. no milk 3:05 leave for store 3:10 arrive at store look in fridge. no milk 3:15 buy milk leave for store 3:20 return home, put milk in fridge arrive at store 3:25 buy milk 3:30 return home, put milk in fridge how can Alice and Bob coordinate better?

47

slide-73
SLIDE 73

too much milk “solution” 1 (algorithm)

leave a note: “I am buying milk”

place before buying remove after buying don’t try buying if there’s a note

≈ setting/checking a variable (e.g. “note = 1”)

with atomic load/store of variable

if (no milk) { if (no note) { leave note; buy milk; remove note; } }

48

slide-74
SLIDE 74

too much milk “solution” 1 (timeline)

if (no milk) { if (no note) {

Alice Bob

if (no milk) { if (no note) { leave note; buy milk; remove note; } } leave note; buy milk; remove note; } }

49

slide-75
SLIDE 75

too much milk “solution” 2 (algorithm)

intuition: leave note when buying or checking if need to buy

leave note; if (no milk) { if (no note) { buy milk; } } remove note;

50

slide-76
SLIDE 76

too much milk: “solution” 2 (timeline)

leave note; if (no milk) { if (no note) {

Alice

buy milk; } } remove note;

but there’s always a note …will never buy milk (twice or once)

51

slide-77
SLIDE 77

too much milk: “solution” 2 (timeline)

leave note; if (no milk) { if (no note) {

Alice

buy milk; } } remove note;

but there’s always a note …will never buy milk (twice or once)

51

slide-78
SLIDE 78

too much milk: “solution” 2 (timeline)

leave note; if (no milk) { if (no note) {

Alice

buy milk; } } remove note;

but there’s always a note …will never buy milk (twice or once)

51

slide-79
SLIDE 79

“solution” 3: algorithm

intuition: label notes so Alice knows which is hers (and vice-versa)

computer equivalent: separate noteFromAlice and noteFromBob variables

leave note from Alice; if (no milk) { if (no note from Bob) { buy milk } } remove note from Alice;

Alice

leave note from Bob; if (no milk) { if (no note from Alice) { buy milk } } remove note from Bob;

Bob

52

slide-80
SLIDE 80

too much milk: “solution” 3 (timeline)

leave note from Alice if (no milk) {

Alice Bob

leave note from Bob if (no note from Bob) { buy milk } } if (no milk) { if (no note from Alice) { buy milk } } remove note from Bob remove note from Alice

53

slide-81
SLIDE 81

too much milk: is it possible

is there a solutions with writing/reading notes?

≈ loading/storing from shared memory

yes, but it’s not very elegant

54

slide-82
SLIDE 82

too much milk: solution 4 (algorithm)

leave note from Alice while (note from Bob) { do nothing } if (no milk) { buy milk } remove note from Alice

Alice

leave note from Bob if (no note from Alice) { if (no milk) { buy milk } } remove note from Bob

Bob

exercise (hard): prove (in)correctness exercise (hard): extend to three people

55

slide-83
SLIDE 83

too much milk: solution 4 (algorithm)

leave note from Alice while (note from Bob) { do nothing } if (no milk) { buy milk } remove note from Alice

Alice

leave note from Bob if (no note from Alice) { if (no milk) { buy milk } } remove note from Bob

Bob

exercise (hard): prove (in)correctness exercise (hard): extend to three people

55

slide-84
SLIDE 84

too much milk: solution 4 (algorithm)

leave note from Alice while (note from Bob) { do nothing } if (no milk) { buy milk } remove note from Alice

Alice

leave note from Bob if (no note from Alice) { if (no milk) { buy milk } } remove note from Bob

Bob

exercise (hard): prove (in)correctness exercise (hard): extend to three people

55

slide-85
SLIDE 85

too much milk: solution 4 (algorithm)

leave note from Alice while (note from Bob) { do nothing } if (no milk) { buy milk } remove note from Alice

Alice

leave note from Bob if (no note from Alice) { if (no milk) { buy milk } } remove note from Bob

Bob

exercise (hard): prove (in)correctness exercise (hard): extend to three people

55

slide-86
SLIDE 86

Peterson’s algorithm

general version of solution see, e.g., Wikipedia we’ll use special hardware support instead

56

slide-87
SLIDE 87

some defjnitions

mutual exclusion: ensuring only one thread does a particular thing at a time

like checking for and, if needed, buying milk

critical section: code that exactly one thread can execute at a time

result of critical section

lock: object only one thread can hold at a time

interface for creating critical sections

57

slide-88
SLIDE 88

some defjnitions

mutual exclusion: ensuring only one thread does a particular thing at a time

like checking for and, if needed, buying milk

critical section: code that exactly one thread can execute at a time

result of critical section

lock: object only one thread can hold at a time

interface for creating critical sections

57

slide-89
SLIDE 89

some defjnitions

mutual exclusion: ensuring only one thread does a particular thing at a time

like checking for and, if needed, buying milk

critical section: code that exactly one thread can execute at a time

result of critical section

lock: object only one thread can hold at a time

interface for creating critical sections

57

slide-90
SLIDE 90

the lock primitive

locks: an object with (at least) two operations:

acquire or lock — wait until lock is free, then “grab” it release or unlock — let others use lock, wakeup waiters

typical usage: everyone acquires lock before using shared resource

forget to acquire lock? weird things happen

Lock(MilkLock); if (no milk) { buy milk } Unlock(MilkLock);

58

slide-91
SLIDE 91

pthread mutex

#include <pthread.h> pthread_mutex_t MilkLock; pthread_mutex_init(&MilkLock, NULL); ... pthread_mutex_lock(&MilkLock); if (no milk) { buy milk } pthread_mutex_unlock(&MilkLock);

59

slide-92
SLIDE 92

xv6 spinlocks

#include "spinlock.h" ... struct spinlock MilkLock; initlock(&MilkLock, "name for debugging"); ... acquire(&MilkLock); if (no milk) { buy milk } release(&MilkLock);

60

slide-93
SLIDE 93

C++ containers and locking

can you use a vector from multiple threads? …question: how is it implemented?

dynamically allocated array reallocated on size changes

can access from multiple threads …as long as not being resized?

61

slide-94
SLIDE 94

C++ containers and locking

can you use a vector from multiple threads? …question: how is it implemented?

dynamically allocated array reallocated on size changes

can access from multiple threads …as long as not being resized?

61

slide-95
SLIDE 95

C++ containers and locking

can you use a vector from multiple threads? …question: how is it implemented?

dynamically allocated array reallocated on size changes

can access from multiple threads …as long as not being resized?

61

slide-96
SLIDE 96

C++ standard rules for containers

multiple threads can read anything at the same time can only read element if no other thread is modifying it can only add/remove elements if no other threads are accessing container

some exceptions, read documentation really carefully

62

slide-97
SLIDE 97

implementing locks: single core

intuition: context switch only happens on interrupt

timer expiration, I/O, etc. causes OS to run

solution: disable them

reenable on unlock

x86 instructions:

cli — disable interrupts sti — enable interrupts

63

slide-98
SLIDE 98

implementing locks: single core

intuition: context switch only happens on interrupt

timer expiration, I/O, etc. causes OS to run

solution: disable them

reenable on unlock

x86 instructions:

cli — disable interrupts sti — enable interrupts

63

slide-99
SLIDE 99

naive interrupt enable/disable (1)

Lock() { disable interrupts } Unlock() { enable interrupts }

problem: user can hang the system:

Lock(some_lock); while (true) {}

problem: can’t do I/O within lock

Lock(some_lock); read from disk /* waits forever for (disabled) interrupt from disk IO finishing */

64

slide-100
SLIDE 100

naive interrupt enable/disable (1)

Lock() { disable interrupts } Unlock() { enable interrupts }

problem: user can hang the system:

Lock(some_lock); while (true) {}

problem: can’t do I/O within lock

Lock(some_lock); read from disk /* waits forever for (disabled) interrupt from disk IO finishing */

64

slide-101
SLIDE 101

naive interrupt enable/disable (1)

Lock() { disable interrupts } Unlock() { enable interrupts }

problem: user can hang the system:

Lock(some_lock); while (true) {}

problem: can’t do I/O within lock

Lock(some_lock); read from disk /* waits forever for (disabled) interrupt from disk IO finishing */

64

slide-102
SLIDE 102

naive interrupt enable/disable (2)

Lock() { disable interrupts } Unlock() { enable interrupts }

problem: nested locks

Lock(milk_lock); if (no milk) { Lock(store_lock); buy milk Unlock(store_lock); /* interrupts enabled here?? */ } Unlock(milk_lock);

65

slide-103
SLIDE 103

naive interrupt enable/disable (2)

Lock() { disable interrupts } Unlock() { enable interrupts }

problem: nested locks

Lock(milk_lock); if (no milk) { Lock(store_lock); buy milk Unlock(store_lock); /* interrupts enabled here?? */ } Unlock(milk_lock);

65

slide-104
SLIDE 104

naive interrupt enable/disable (2)

Lock() { disable interrupts } Unlock() { enable interrupts }

problem: nested locks

Lock(milk_lock); if (no milk) { Lock(store_lock); buy milk Unlock(store_lock); /* interrupts enabled here?? */ } Unlock(milk_lock);

65

slide-105
SLIDE 105

naive interrupt enable/disable (2)

Lock() { disable interrupts } Unlock() { enable interrupts }

problem: nested locks

Lock(milk_lock); if (no milk) { Lock(store_lock); buy milk Unlock(store_lock); /* interrupts enabled here?? */ } Unlock(milk_lock);

65

slide-106
SLIDE 106

xv6 interrupt disabling (1)

... acquire(struct spinlock *lk) { pushcli(); // disable interrupts to avoid deadlock ... /* this part basically just for multicore */ } release(struct spinlock *lk) { ... /* this part basically just for multicore */ popcli(); }

66

slide-107
SLIDE 107

xv6 push/popcli

pushcli / popcli — need to be in pairs pushcli — disable interrupts if not already popcli — enable interrupts if corresponding pushcli disabled them

don’t enable them if they were already disabled

67

slide-108
SLIDE 108

68

slide-109
SLIDE 109

backup slides

69

slide-110
SLIDE 110

thread versus process state

thread state — kept in thread control block

registers (including program counter)

  • ther information?

process state — kept in process control block

address space (memory layout)

  • pen fjles

process id …

70

slide-111
SLIDE 111

Linux idea: task_struct

Linux model: single “task” structure = thread pointers to address space, open fjle list, etc. pointers can be shared — if same process fork()-like system call “clone”: choose what to share

clone(CLONE_FILES, ...) — new process sharing open fjles clone(CLONE_VM, ...) — new process sharing address spaces

advantage: no special logic for threads (mostly)

71

slide-112
SLIDE 112

Linux idea: task_struct

Linux model: single “task” structure = thread pointers to address space, open fjle list, etc. pointers can be shared — if same process fork()-like system call “clone”: choose what to share

clone(CLONE_FILES, ...) — new process sharing open fjles clone(CLONE_VM, ...) — new process sharing address spaces

advantage: no special logic for threads (mostly)

71

slide-113
SLIDE 113

aside: alternate threading models

we’ll talk about kernel threads OS scheduler deals directly with threads alternate idea: library code handles threading kernel doesn’t know about threads w/in process hierarchy of schedulers: one for processes, one within each process not currently common model — awkward with multicore

72

slide-114
SLIDE 114

why threads?

concurrency: difgerent things happening at once

  • ne thread per user of web server?
  • ne thread per page in web browser?
  • ne thread to play audio, one to read keyboard, …?

parallelism: do same thing with more resources

multiple processors to speed-up simulation (life assignment)

73