All examples are in a fictional language very close to Javascript.
Short:
A race condition may occur between two or more threads. We cannot have race conditions inside a single thread process (for example, in a single thread that does not execute I / O programs).
But in many cases, a single thread program can be used:
give situations similar to race conditions, for example, in a program based on events with an event cycle, but are not real race conditions
causes a race condition between or with other threads, for example:
- other programs such as clients
- threads or server libraries
I) Race conditions may occur only between two or more streams
A race condition can occur only when two or more threads try to access a shared resource, not knowing that it is being modified at the same time by unknown instructions from another thread (s). This gives an uncertain result . (This is really important.)
A process with a single thread is nothing more than a sequence of known instructions , so the result is determined even if the order in which the instructions are executed is not easy to read in the code.
II) But we are unsafe
II.1) Situations similar to race conditions
Many programming languages implement asynchronous programming functions through events or signals processed by the main loop or event loop, which check the queue of events and trigger listeners. An example of this is Javascript, libuevent, reactPHP, GNOME GLib ... Sometimes we can find situations that appear to be race conditions , but they don’t .
The method of invoking the event loop is always known , so the result is determined even if the order in which the instructions are executed is not easy to read (or cannot even read if we do not know the library).
Example:
setTimeout( function() { console.log("EVENT LOOP CALLED"); }, 1 ); // We want to print EVENT LOOP CALLED after 1 milliseconds var now = new Date(); while(new Date() - now < 10) //We do something during 10 milliseconds console.log("EVENT LOOP NOT CALLED");
in Javascript output always (you can test in node.js):
EVENT LOOP NOT CALLED EVENT LOOP CALLED
because the event loop is called when the stack is empty (all functions returned).
Keep in mind that this is just an example, and that in languages that implement events differently, the result may be different, but it will still be determined by the implementation.
II.2) The condition of the race between other threads, for example:
II.2.i) With other programs, such as clients
If other processes request our process, our program does not process requests atomically, and that our process shares some resources between requests, there may be a race condition between clients.
Example:
var step; on('requestOpen')( function() { step = 0; } ); on('requestData')( function() { step = step + 1; } ); on('requestEnd')( function() { step = step +1;
Here we have a classic setting of race conditions. If the request is opened before the other end, step will be reset to 0. If two requestData events are requestData before requestEnd due to two simultaneous requests, the step will be reached 3. But this is because we accept the sequence of events as undefined. We expect that the result of the program in most cases is not defined with an uncertain input.
In fact, if our program is a single stream, taking into account the sequence of events , the result is still always determined. The race condition between customers .
There are two ways to understand a thing:
- We can consider customers as part of our program (why not?), In which case our program is multithreaded. The end of the story.
- More often than not, we can assume that clients are not part of our program. In this case, they are just input . And when we consider whether the program has a certain result or not, we do this with the entered value . Otherwise, even the simplest program
return input; would have an uncertain result.
Note that:
- If our process processes the request atomically, it is the same as if there was a mutual exchange between the client and there is no race condition.
- if we can identify the request and attach a variable to the request object, which will be the same at each step of the request, there is no common resource between customers and race conditions
II.2.ii) Using the library (s)
In our programs, we often use libraries that spawn other processes or threads, or simply do I / O with other processes (and I / O is always undefined).
Example:
databaseClient.sendRequest('add Me to the database'); databaseClient.sendRequest('remove Me from the database');
This can cause a race condition in the asynchronous library. This is so if sendRequest() returned after sending the request to the database, but before the request is actually executed. We immediately send another request, and we cannot know whether the first will be executed before the second is evaluated, because the database is running in a different thread. There is a race condition between the program and the database process .
But, if the database was in the same thread as the program (which in real life does not often happen), it would be impossible for sendRequest to return before processing the request. (If the request is not queued, but in this case the result is still determined , because we know exactly how and when the queue is read.)
Conclusion
In short, single-threaded programs do not exempt race conditions from triggers. But they can only occur with other flows of external programs or between them. The result of our program may be uncertain because the input that our program receives from these other programs is not defined.