Volatile Introduction
Volatile is similar to the well-known const and is also a type modifier. Volatile indicates to the compiler that the objects it modifies should not be optimized. Volatile is used for multithreaded programming. In a single thread, it can only limit Compiler optimization. Therefore, single-threaded shoes do not have to waste time looking at the following.
No volatile results
Without volatile, you cannot use basic variables in parallel in multiple threads. The following is an example of my Development Project (this instance uses the C # language, but it does not prevent us from discussing C ++ ). In the development of A. Net project in the school, I used a basic variable int32 in multi-thread monitoring. I used it to control a condition for monitoring in multiple threads. Considering that the basic variables are built by the compiler and cannot be locked by the lock, I think that atomic operations do not have the problem of multithreading. After the actual operation, I can find that the program is running normally and sometimes abnormal, it is normal to use a dictionary object for processing and locking. Now we want to operate the variable in multiple threads at the same time. The specific content will be explained below.
Role of volatile
If a basic variable is modified by volatile, the compiler will not save it to the register, but will access the location where the variable is actually saved in the memory every time. This avoids the catastrophic problems caused by Compiler optimization caused by variables without volatile modification in multi-thread read/write. Therefore, the volatile modifier must be added to the basic variables that must be shared in multiple threads. Of course, volatile also allows you to capture non-thread-safe code during compilation. I will also introduce how a Daniel uses smart pointers to sort the code in the sharing area. I would like to express my gratitude to him.
Generic programming once said that writing code with exceptional security is very difficult, but this is too minor compared to the difficulty of multi-threaded programming. In multi-thread programming, you need to prove that it is correct, and you need to debug and fix it repeatedly. Of course, resource competition must also be noted. The most hateful thing is, sometimes the compiler will give you some color to see...
Class student
{
Public:
Void wait () // It is really painful to wait in line for dinner at Beihang...
{
While (! Flag)
{
Sleep (1000); // sleeps for 1000 milliseconds
}
}
Void eat ()
{
Flag = true;
}
...
PRIVATE:
Bool flag;
};
Well, you can wait for dinner in multiple threads, but it is estimated that you will never be able to wait here, because the flag is put into the register by the compiler, even if the boy shoes in front of you tell you that flag is true, you can't see them anymore. This strange situation occurs because the judgment value you used is previously saved in the register, so that you have not obtained the flag value from the original address. What should I do? By the way, change to volatile to solve the problem.
Volatile differs from const in basic and custom types. For example, you can assign a non-volatile value to volatile, however, you cannot assign a non-volatile value of the User-Defined type to volatile, while const is acceptable. Another difference is that the copy control of the compiler's automatic synthesis is not applicable to volatile objects, because the members of the merging copy control receive the const parameters, which are references to the const of the class type, however, the volatile object cannot be passed to a common reference or const reference.
How to use volatile in multiple threads
In multithreading, we can use the lock mechanism to protect the resource critical section. Volatile is required to operate shared variables outside the critical section, and non-volatile is required in the critical section. We need a tool class lockingptr to save the mutex collection and volatile conversion using const_cast (using const_cast to convert volatile ).
First, we declare a framework of the mutex class used in lockingptr:
Class mutex
{
Public:
Void acquire ();
Void release ();
...
};
Then declare the most important lockingptr template class:
Template <typename T>
Class lockingptr {
Public:
// Constructors/Destructors
Lockingptr (volatile T & OBJ, mutex & CTX)
: Pobj _ (const_cast <t *> (& OBJ )),
Pcontent _ (& content-as-you-go)
{CTX. Lock ();}
~ Lockingptr ()
{Pctx _-> unlock ();}
// Pointer Behavior
T & operator *()
{Return * pobj _;}
T * operator-> ()
{Return pobj _;}
PRIVATE:
T * pobj _;
Mutex * pmt _;
Lockingptr (const lockingptr &);
Lockingptr & operator = (const lockingptr &);
};
Although this class looks simple, it is very useful in the preparation of the multi-threaded program. You can use it to make operations on shared objects in multiple threads as simple as the basic variables modified by volatile and never use const_cast. Here is an example:
Assume that two threads share a vector <char> object:
Class syncbuf {
Public:
Void thread1 ();
Void thread2 ();
PRIVATE:
Typedef vector <char> buft;
Volatile buft buffer _;
Mutex mt_; // controls access to buffer _
};
In the thread1 function, you use lockingptr <buft> to control access to the buffer _ member variable:
Void syncbuf: thread1 (){
Lockingptr <buft> lpbuf (buffer _, CTX _);
Buft: iterator I = lpbuf-> begin ();
For (; I! = Lpbuf-> end (); ++ I ){
... Use * I...
}
}
This code is easy to write and understand. As long as you need to use buffer _, you must create a lockingptr <buft> pointer to point to it. Once you do this, you get the entire interface of the container vector. And once you make a mistake, the compiler will point out:
Void syncbuf: thread2 (){
// Error! Cannot access 'begin' for a volatile object
Buft: iterator I = buffer _. Begin ();
// Error! Cannot access 'end' for a volatile object
For (; I! = Lpbuf-> end (); ++ I ){
... Use * I...
}
}
In this way, you can only access member functions and variables through const_cast or lockingptr. The difference between the two methods is that the latter provides sequential methods for implementation, while the former is implemented by converting to volatile. Lockingptr is quite understandable. If you need to call a function, you can create an unnamed temporary lockingptr object and use it directly:
Unsigned int syncbuf: size (){
Return lockingptr <buft> (buffer _, CTX _)-> size ();
}
Use lockingptr in Basic Types
As described above, volatile is used to protect unexpected access to objects and lockingptr is used to provide simple and efficient multi-threaded code. Now we will discuss one of the common basic types of multi-threaded processing and sharing:
Class counter
{
Public:
...
Void increment () {++ CTR _;}
Void decrement () {-- CTR _;}
PRIVATE:
Int CTR _;
};
At this time, you may be able to see the problem. 1. CTR _ must be of the volatile type. 2. Even if it is ++ CTR _ or -- CTR _, this still requires three atomic operations (read-Modify-write) in processing ). Based on the above two points, this class has problems in multithreading. Now we can use lockingptr to solve the problem:
Class counter
{
Public:
...
Void increment () {++ * lockingptr <int> (CTR _, CTX _);}
Void decrement (){-? * Lockingptr <int> (CTR _, CTX _);}
PRIVATE:
Volatile int CTR _;
Mutex mt _;
};
Volatile member functions
For a class, if the class is volatile, all the Members in the class are volatile. Second, declare the member function as volatile, and declare it at the end of the function just like Const. When designing a class, the volatile member functions you declare are thread-safe, so those functions that may be called at any time should be declared as volatile. Considering that volatile is equal to the thread safety code and non-critical section, non-volatile is equal to the single-thread scenario and in the critical section. We can use the volatile overload of this function to make a trade-off between thread security and speed first. The specific implementation is omitted here.
Summary
Four key points for Using volatile in multi-threaded programming:
1. Declare all shared objects as volatile;
2. Do not apply volatile directly to the basic type;
3. When shared classes are defined, the volatile member function is used to ensure thread security;
4. Learn more about and use volatile and lockingptr! (Strongly recommended)