1. The role of volatile keywords
Under normal circumstances, the compiler will optimize the code.
For example, if a variable does not change in a piece of code, the compiler may cache it into a register and no longer read from memory. But in some cases, the value of the variable may beOutside the programChanges occur, such as multithreaded access, hardware registers, asynchronous events, etc.
If the compiler optimizes these variables, it may cause unpredictable errors in the program.
Problems that volatile can solve:
- Prevent compiler optimization so that variables read the latest value from memory every time
- Make sure that the value of the variable is not cached by the register
- Suitable for multithreading, hardware registers and other scenarios
Problems that volatile cannot be solved:
- volatile cannot guarantee thread safety
- volatile cannot guarantee the atomicity of multiple operations
- To implement thread synchronization, you should use std::atomic or mutex
2. Use scenarios of volatile keywords
1. Multi-threaded shared variables
In a multithreaded environment, one thread may modify the variable, while another thread needs to detect the change of the variable.volatile
Make sure that the thread reads the latest value each time, not the compiler optimized cached value.
Example:
#include <iostream> #include <thread> volatilebool stopFlag = false; // Use the volatile keyword void worker() { while (!stopFlag) { // If there is no volatile, the old value may be read all the time std::cout << "Worker is running..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } std::cout << "Worker stopped." << std::endl; } int main() { std::thread t(worker); std::this_thread::sleep_for(std::chrono::seconds(3)); stopFlag = true; // Another thread modify stopFlag (); return0; }
explain:
-
volatile bool stopFlag
make sureworker
Thread can detectmain
Thread pairstopFlag
modification. - If not used
volatile
, the compiler may optimizestopFlag
read, letworker
Thread infinite loop. -
but
volatile
No thread safety guaranteedIf multiple threads are involved in synchronization, it is recommended to usestd::atomic<bool>
replace.
2. Access hardware registers
In embedded development, hardware registers (such as I/O ports, device status registers, etc.) are usually required, and the values of these registers may change at any time. usevolatile
Make sure that every visit is up to date.
Example (embedded system):
#define STATUS_REGISTER (*(volatile unsigned int*)0x40001000) void checkStatus() { while (STATUS_REGISTER & 0x01) { // Read the status register // Wait for the state to change } }
explain:
-
volatile
make sureSTATUS_REGISTER
Will not be optimized by the compiler, read the latest value from the hardware register every time you access. - existEmbedded developmentIn, visitI/O ports, sensor dataWhen it is usually necessary
volatile
。
3. Prevent compiler optimization
In some cases, we may need to insert one into the codeEmpty loopto perform a short delay, but if not usedvolatile
, the compiler may directly optimize this loop, causing the code to not execute as expected.
Example:
void delay() { for (volatile int i = 0; i < 1000000; i++); // Prevent optimization of loop loss}
explain:
-
volatile
Ensure loop variablesi
Read from memory every time, will not be optimized by the compiler. - This usage is commonTime delay, busy waitingetc.
4. Handle asynchronous events
Some programs may handle asynchronous events (e.g.InterruptorSignal), the value of the variable may be modified at an unknown point in time. usevolatile
Enable the main program to correctly read the latest value of the variable.
Example (simulated interrupt processing):
volatile bool interruptFlag = false; // Interrupt flag void interruptHandler() { // Assume it is an interrupt service function interruptFlag = true; } void checkInterrupt() { while (!interruptFlag) { // Wait for an interrupt to occur } std::cout << "Interrupt received!" << std::endl; }
explain:
-
interruptFlag
Probably inInterrupt handlerwas modified, sovolatile
Make sure the latest value is read every time. - If not
volatile
, the compiler may be optimizedwhile (!interruptFlag)
This part of the code turns it into a dead loop.
3. volatile vs std::atomic
In multithreaded programming,volatile
JustPrevent compiler optimization,butcannotEnsure thread safety. For example:
volatile int counter = 0; void increment() { for (int i = 0; i < 100000; i++) { counter++; // It's still not thread-safe } }
question:
-
counter++
Not an atomic operation, it containsRead, add, write backThree steps, which may lead to data competition in a multi-threaded environment.
Recommended usestd::atomic
replace:
#include <atomic> std::atomic<int> counter = 0; void increment() { for (int i = 0; i < 100000; i++) { counter++; // Thread safety } }
Summarize:
-
volatile
Suitable for preventing optimization, but does not guarantee thread safety. -
std::atomic
It can not only prevent optimization, but also ensure the atomicity of the operation.
4. Experience
Use scenarios |
volatile The role of |
---|---|
Multithreaded variables | Make sure that every read is the latest value (but not thread-safe) |
Hardware Registers | Access I/O ports or status registers to prevent compiler optimization |
Prevent optimization | Make variables not cached by registers, ensuring that loops and other codes are not optimized |
Asynchronous Events | Handle asynchronous situations such as interrupts and signals to ensure that the main program correctly reads the latest value |
V. Conclusion
-
volatile
Applicable toMultithreading, hardware registers, asynchronous eventsScenarios, but itNo thread safety guaranteed。 - existMulti-threaded environmentDown,Recommended use
std::atomic
Insteadvolatile
**,becausestd::atomic
Can guaranteeThread safety and visibility。 - existEmbedded developmentmiddle,
volatile
StillBest choice for accessing hardware registers。
The above is personal experience. I hope you can give you a reference and I hope you can support me more.