Http: //dev.9 Press .NET / DEVELOP / Article / 83/83923.
It's not that I deliberately want to show your mood, but in this column, we will discuss this topic of multithreading programming. As mentioned in the previous period, the program writes an exception-safe is very difficult, but it is simply a play with a multi-threader program. Multi-threaded procedures are difficult to write, difficult to verify, difficult to debug, difficult to maintain, which is often bitter. Incorrect multi-threading programs may be able to run for many years, do not have a mistake until they meet certain critical conditions, only unexpected strange mistakes. Needless to say, programmers who write multithreaded programs need to use all possible helpments. This column will focus on discussing competitive conditions - this is usually the root of various troubles in multi-threaded programs - deeply understanding it and provides some tools to prevent competition. Amazing is that we will make the compiler to help you do these things. Just just an opaque keyword. Although C and C standards are obvious "keep silent" for threads, they do a privilege for multi-threaded privileges in the form of Volatile keywords. Just like a more familiar const, Volatile is a Type Modifier. It is designed to modify variables that are accessed and modified by different threads. If there is no volatile, it will basically lead to such results: Either you cannot write multithreaded programs, or the compiler will lose a lot of optimization opportunities. Let's explain one by one. Consider the following code: Class gadget {public: void wait () {while (! Flag_) {sleep (1000); // sleeps for 1000 milliseconds}} void wakeup () {flag_ = true;} ... private : bool flag_;}; The purpose of gadget :: wait in the above code is to check the FLAG_ member variable every one second. When Flag_ is set to True by another thread, the function returns. At least this is the intent of the executive author, however, this WAIT function is wrong. Suppose the compiler finds that Sleep (1000) is called an external library function, which does not change the member variable flag_, then the compiler can determine that it can put the FLAG_ cocked in the register, and you can access the register to replace accesses slower Memory on the motherboard. This is a good optimization for single-threaded code, but in this case, it destroys the correctness of the program: When you call a gadget's WAIT function, even if another thread calls WAKEUP, WAIT will still be loop. This is because the change of FLAG_ is not reflected in the register of the cache. The optimization of the compiler is not awkward ... optimistic. In most cases, it is a very valuable optimization method in the register, and it is a pity. C and C provide you with an opportunity to disable this cache optimization. If you declare that the variable is using the volatile modifier, the compiler does not cause this variable to cushically in the register - each access will go to the actual location of the variable in memory. This way you have to make changes to the gadget's wait / Wakeup is to add the correct modifications to FLAG_: Class gadget {public: ... as Above ... private: volatile bool flag_;}; Most of the principle of Volatile and The interpretation of the usage will be here, and it is recommended that you use the volatile to modify the native type variable used in multiple threads. However, you can do more things with volatile because it is part of a magical C type system.
Try the Volatile to customize type volatile modifications not only for native types, but also for custom types. At this time, the volatile modification is similar to const (you can also use const and volatile for a type.). Unlike const, Volatile's role is different from the native type and custom type. That is to say, when the native type has Volatile modification, it still supports their various operations (plus, multiplied, assignment, etc.), but for Class, it is not the case. For example, you can assign a non-Volatile's intimate value to an int, but you can't assign a non-Volatile object to a Volatile object. Let us give an example to illustrate how the custom type Volatile works. Code: Class gadget {public: void foo () volatile; void bar (); ... private: string name_; int 2_;}; gadget regulagadget; volatile gadget volatilegadget; if you think volatile is not What role, then you have to be shocked. Volatilegadget.foo (); // ok, volatile fun called for // volatile Object regulagadget.foo (); // ok, volatile fun caled for // Non-Volatile Object volatilegadget.bar (); // error! Non- Volatile Function Called for // Volatile Object! The conversion from the type without Volatile modified to the corresponding Volatile type is usually usually. However, just like const, you can't turn over the Volatile type to a nonvatile type. You must use type conversion operators: gadget & ref = const_cast
Mutex is used to avoid competitive conditions in data. According to the definition, the so-called competitive condition is such a situation: the role of multiple threads to depends on the scheduling order of the thread. Competitive conditions will occur when the two threads are competing to access the same data. Because a thread can interrupt other threads at any time, the data may be destroyed or interpreted. Therefore, the modification operation of the data must be protected by critical regions in critical regions. In object-oriented programming, this usually means you save a MUTEX in a member variable, then use this MUTEX when you access this class. Multi-threaded programming masters have seen the above two paragraphs, may already beaten, but their purpose is to provide an preparing exercise, we have to contact Volatile. We will make a comparison of the type of C and threads. In a critical area, any thread breaks other threads at any time; this is not controlled, so variables accessed by multiple threads are easily changed. This is consistent with the origin of Volatile, so you need to use Volatile to prevent the compiler from unintentionally cache such variables. In a critical area defined by a Mutex, only one thread can enter. Therefore, the code executed in the critical regions has the same semantics as the single-threaded program. The controlled variable will not be changed again - you can remove the volatile modification. In short, the data shared between the thread is outside the critical area, and it is not within the critical area. You entered a critical zone by locking a MUTEX, then you remove a type of volatile modification, if we can successfully put these two operations together, then we are in C type systems and applications. The thread semantics establishes contact. This way we can let the compiler to help us detect competitive conditions. LockingPtr We need a tool to make Mutex acquisition and const_cast. Let's design a LockingPtr class, you need to use a volatile object OBJ and a MUTEX object MTX to initialize it. In the life-Lodging period of the LockingPtr, it guarantees that MTX is being acquired, and also provides access to OBJ-removing Volatile modified OBJ. Access to OBJ is similar to Smart Pointer, which is performed by Operator-> and Operator *. Const_cast is conducted within the LockingPtr. This transformation is correct in semantics because LockingPtr has Mutex in its survival. First, let's define the Mutex classes for work with LockingPtr: Code: Class Mutex {public: void acquire (); void release (); ...}; To use LockingPtr, you need to use the data structure provided by the operating system And the underlying function to achieve MUTEX. LockingPtr is a template that uses the type of control variable as a template parameter. For example, if you want to control a Widget, you have to write LockingPtr
Code: template
If you only need to call a function, you can create an unknown temporary LockingPtr object, then use it directly: Code: unsigned int syncbuf :: size () {Return LockingPtr
Don't forget: Volatile is equivalent to free multi-thread code, and there is no critical region; non-Volatile is equivalent to a single-threaded environment or within the critical area. For example, you define a Widget class that implements the same operation with two ways - a method of thread security and a fast unprotected approach. Code: Class Widget {public: void Operation () Volatile; void Operation (); ... private: mutex mtx_;}; Note Here the overloading. Now Widget's users can call Operation in a consistent syntax, and thread security can be obtained for the Volatile object, and the speed can be obtained for normal objects. Users must pay attention to define the shared Widget object as Volatile. When implementing the Volatile member function, the first operation is usually to lock this with LockingPtr, then the rest can be handed over to the same name function for non-Volatile: Code: void widget :: Operation () Volatile {LockingPtr
About Volatile Correctness, the most important question is that it relies on Mutex similar to POSIX. If you are in multiprocessor systems, it is not enough - you must use Memory Barriers. Another more philosophical problem is: Strictly transform the Volatile property of the variable through type conversion, even if the Volatile property is yourself for Volatile Correctness. As Anthony Williams pointed out, you can imagine that a system may put the Volatile data in a storage area different from non-Volatile data, in which case the address transformation has uncertain behavior. Another criticism is that Although Volatile Correctness can solve competitive conditions at a lower level, it is not possible to correctly detect high-level, logical competitive conditions. For example, you have a MT_Vector template class, used to simulate the std :: Vector, and the member function is synchronized by the correct thread. Consider this code: volatile mt_vector
http://blog.9cbs.net/lphpc/