Reputation: 1821
I have a global variable named my_list
containing a linked list (i.e., the variable is a pointer to the first member of the list). This variable can be edited only by two threads, thread A and thread B. In case the list becomes empty, the variable is set to NULL
. When this happens the variable is set to NULL
first, and afterwards the memory is freed.
As it is edited by two threads, I use a mutex every time thread A or thread B touch the my_list
variable. Nothing unusual so far.
But then comes a third thread, thread C. This thread will never ever touch the linked list in any way, but it will need to know from time to time whether the list is empty or not. So, the only thing thread C will do is
if (my_list) {
do_something_completely_unrelated();
}
Do I have to use a mutex for that? I believe it is an atomic operation, so no mutex is required. Is this correct?
EDIT
I will add here a bit of context. The reason why I would like to avoid using a mutex is that the list gets updated rarely (always), but the check coming from thread C happens every few milliseconds, so the less operations the better.
If the list appears to be non-NULL
, then a proper check using a mutex is triggered by thread C, and if it is confirmed that the list is not empty thread C stops its obsessive check.
Upvotes: 4
Views: 1246
Reputation: 31409
According to the standard, it is undefined behavior to read a variable when it's written if at least one of the operations is non-atomic.
From comment section, you seem to wonder if it will crash your program, or if the worst thing that may happen is that you get the wrong value. Your comment, emphasis mine:
Thank you. But concretely what could happen bad? Let's say the variable is
NULL
but is being changed right now by thread A to addressxxxxxx
. Thread C attempts to read it. What value can it get? EitherNULL
orxxxxxx
, and both are fine. Am I wrong in assuming that the program will not crash?
I would say that this simple check will most likely not cause your program to crash. The check operation is safe in the sense that you will most likely get a value. The value may be wrong, but the check alone will most likely not crash your program.
However, it IS undefined behavior:
The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.
C18 Standard, section 5.1.2.4, paragraph 35
Then you posted this follow up comment:
This is actually not so important, but am I correct in assuming that if thread A is changing the value from
NULL
toxxxxxx
(or vice versa) I will definitely not getyyyyyy
, but either NULL orxxxxxx
?
Here I'd say your assumption is wrong. AFIK, there's nothing in the standard that says that a pointer assignment needs to be an atomic operation, and I would be surprised if it was. And as I mentioned above, it IS undefined behavior.
_Atomic
keywordDeclare the list with the _Atomic
qualifier. It has some limitations. For instance:
It's not a mandatory feature, so it might reduce portability
It cannot be used with arrays
If used with a struct, the fields of the struct cannot be accessed individually
2 and 3 should not matter to you, since the list is just a pointer.
Read about _Atomic
here: https://en.cppreference.com/w/c/language/atomic
If _Atomic
is not an option, here is a solution in pseudo code:
if ( now() - lastUpdated > ms ) // If it was more than ms milliseconds since last update
lock(myMutex)
myListCopy = myList
unlock(myMutex)
lastUpdated = now()
if(myListCopy)
do_something_completely_unrelated();
lastUpdated
and myListCopy
are preferably variables that are local to the function, but the important part is that thread A and B never touches them.
Upvotes: 5