How does thread context-switching work with global variable?

1.2k views Asked by At

I have been confused at this question:
I have C++ function:

void withdraw(int x) {
     balance = balance - x;
}

balance is a global integer variable, which equals to 100 at the start.
We run the above function with two different thread: thread A and thread B. Thread A run withdraw(50) and thread B run withdraw(30).

Assuming we don't protect balance, what is the final result of balance after running those threads in following sequences?

  1. A1->A2->A3->B1->B2->B3
  2. B1->B2->B3->A1->A2->A3
  3. A1->A2->B1->B2->B3->A3
  4. B1->B2->A1->A2->A3->B3

Explanation:

  • A1 means OS execute the first line of function withdraw in thread A, A2 means OS execute the second line of function withdraw in thread A, B3 means OS execute the third line of function withdraw in thread B, and so on.

  • The sequence is how OS schedule thread A & B presumably.

My answer is

  1. 20
  2. 20
  3. 50 (Before context switch, OS saves balance. After context switch, OS restore balance to 50)
  4. 70 (Similar to above)

But my friend disagrees, he said that balance was a global variable. Thus it is not saved in stack, so it does not affected by context switching. He claimed that all 4 sequences result in 20.

So who is right? I can't find fault in his logic.

(We assume we have one processor that can only execute one thread at a time)

5

There are 5 answers

2
MikeMB On

Simple and short answer for c++: Unsynchronized access to a shared variable is undefined behavior, so anything can happen. The value can e.g. be 100,70,50,20,42 or -458995. The program could crash or not. And in theory its even allowed to order pizza.

The actual machine code that is executed is usually far away from what your program looks like and in the case of undefined behavior, you are no longer guaranteed, that the actual behavior has anything to do with the c++ code you have written.

7
David Schwartz On

Unless the threading standard you are using specifies, then there's no way to know. Most typical threading standards don't, so typically there's no way to know.

Your answer sounds like nonsense though. The OS has no idea what balance is nor any way to do anything to it around a context switch. Also, threads can run at the same time without context switches.

Your friend's answer also sounds like nonsense. How does he know that it won't be cached in a register by the compiler and thus some of the modifications will stomp on previous ones?

But the point is, both of you are just guessing about what might happen to happen. If you want to answer this usefully, you have to talk about what is guaranteed to happen.

1
gnasher729 On

Clearly homework, but saved by doing actual work before asking.

First, forget about context switching. Context switching is totally irrelevant to the problem. Assume that you have multiple processors, each executing one thread, and each progressing at an unknown speed, stopping and starting at unpredictable times. Even better, assume that this stopping and storing is controlled by an enemy, who will try to break your program.

And because context switching is irrelevant, the OS will not save or restore anything. It won't touch the variable balance. Only your two threads will.

Your friend is absolutely, totally wrong. It's quite the opposite. Since balance is a global variable, both threads can read and write it. But you don't only have the problem that they might read and write it in unknown order, as you examined, it is worse. They could access it at the same time, and if one thread modifies data while another reads it, you have a race condition and anything at all could happen. Not only could you get any result, your program could also crash.

If balance was a local variable saved on the stack, then both threads would have each its own variable, and nothing bad would happen.

0
Jim Flood On

Consider this line:

balance = balance - x;

Thread A reads balance. It is 100. Now, thread A subtracts 50 and ... oops

Thread B reads balance. It is 100. Now, thread B subtracts 30 and updates the variable, which is now 70.

...thread A continues and updates the variable, which is now 50. You've just completely lost the work of Thread B.

Threads don't execute "lines of code" -- they execute machine instructions. It does not matter if a global variable is affected by context switching. What matters is when the variable is read, and when it is written, by each thread, because the value is "taken off the shelf" and modified, then "put back". Once the first thread has read the global variable and is working with the value "somewhere in space", the second thread cannot read the global variable until the first thread has written the updated value.

0
Jim Flood On

Consider this line:

balance = balance - x;

Thread A reads balance. It is 100. Now, thread A subtracts 50 and ... oops

Thread B reads balance. It is 100. Now, thread B subtracts 30 and updates the variable, which is now 70.

...thread A continues now updates the variable, which is now 50. You've just lost the work that Thread B.

Threads don't execute "lines of code" -- they execute machine instructions. It does not matter if a global variable is affected by context switching. What matters is when the variable is read, and when it is written, by each thread, because the value is "taken off the shelf" and modified, then "put back". Once the first thread has read the global variable and is working with the value "somewhere in space", the second thread must not read the global variable until the first thread has written the updated value.