Let's assume there is a data structure like a std::vector and a global variable int syncToken initialized to zero. Also given, exactly two threads as reader/writer, why is the following (pseudo) code (in)valid?
void reader_thread(){
while(1){
if(syncToken!=0){
while(the_vector.length()>0){
// ... process the std::vector
}
syncToken = 0; // let the writer do it's work
}
sleep(1);
}
}
void writer_thread(){
while(1){
std::string data = waitAndReadDataFromSomeResource(the_resource);
if(syncToken==0){
the_vector.push(data);
syncToken = 1; // would syncToken++; be a difference here?
}
// drop data in case we couldn't write to the vector
}
}
Although this code is not (time-)efficient, as far as I can see the code is valid, because the two threads only synchronize on the global variable value in a way such that no undefined behaviour could result. The only problem might occur at using the vector concurrently, but this shouldn't happen because of only switching between zero and one as a synchronization value, right?
UPDATE Since I made the mistake of asking just a yes/no question, I updated my question to why in hope of getting a very specific case as an answer. It also seems that the question itself draws the wrong picture based on the answers so I'll elaborate more on what my problem/question is with above code.
Beforehand, I want to point out that I'm asking for a specific use case/example/proof/detailed explanation which demonstrates exactly what goes out of sync. Even a C example code which let a an example counter behave non monotonic increasing would just answer the yes/no question but not why! I'm interested in the why. So, if you provide an example which demonstrates that it has a problem I'm interested in the why.
By (my) definition above code shall be named synchronized if and only if the code within the if statement, excluding the syncToken assignment at the bottom of the if block, can only be executed by exactly one of those two given threads at a given time.
Based on this thought I'm searching for a, maybe assembler based, example where both threads execute the if block at the same time - meaning they are out of sync or namely not synchronized.
As a reference, let's look at the relevant part of assembler code produced by gcc:
; just the declaration of an integer global variable on a 64bit cpu initialized to zero
syncToken:
.zero 4
.text
.globl main
.type main, @function
; writer (Cpu/Thread B): if syncToken == 0, jump not equal to label .L1
movl syncToken(%rip), %eax
testl %eax, %eax
jne .L1
; reader (Cpu/Thread A): if syncToken != 0, jump to Label L2
movl syncToken(%rip), %eax
testl %eax, %eax
je .L2
; set syncToken to be zero
movl $0, syncToken(%rip)
Now my problem is that, I don't see a way why those instructions can get out of sync.
Assume both threads run on their own CPU core like Thread A runs on core A, Thread B runs on core B. The initialization is global and done before both threads begin execution, so we can ignore the initialization and assume both Threads start with syncToken=0;
Example:
- Cpu A: movl syncToken(%rip), %eax
- Cpu A: context switch (saving all registers)
- Cpu B: movl syncToken(%rip), %eax
- Cpu B: testl %eax, %eax
- Cpu B: jne .L1 ; this one is false => execute writer if block
- Cpu B: context switch
- Cpu A: context switch to thread (restoring all registers)
- Cpu A: testl %eax, %eax
- Cpu A: je .L2 ; this is false => not executing if block
Honestly I've constructed an example which works well, but it demonstrates that I don't see a way why the variable should go out of sync such that both threads execute the if block concurrently. My point is: although the context switch will result in an inconsistency between %eax and the actual value of syncToken in RAM, the code should do the right thing and just not execute the if block if it is not the single only thread allowed to run it.
UPDATE 2 It can be assumed that syncToken will only be used like in the code as shown. No other function (like waitAndReadDataFromSomeResource) is allowed to use it in any way
UPDATE 3 Let's go one step further by asking a slight different question: Is it possible to synchronize two threads, one reader, one writer using an int syncToken such that the threads won't go out of sync all time by executing the if block concurrently? If yes - that's very interesting ^^ If no - why?
syncToken
?std::atomic
? – MuumuusyncToken
... – MuumuuwaitAndReadDataFromSomeResource
do? You need to specify in particular the wait part. Does that thread block until data can be read from the source? – Nighttimeint
we are talking about is not evenvolatile
to begin with. If it is not volatile, nothing will work. Does this really need an explanation? – Overdose