[Post] considerations for MFC multi-thread programming Favorites 1. Performance-error example About the window object (pointer? Handle ?) Problems: After selecting the start thread in the menu: Void cmainframe: onmenu_start () { ... Afxbeginthread (mythread, this ); ... } The thread functions are as follows: Uint mythread (lpvoid pparam) { Cmainframe * pmainfrm = (cmainframe *) pparam; ... } Question 1: SuchCodeIs there a problem? (The document says that the pointer to the MFC object cannot be directly transmitted between threads. It should be implemented through the transfer handle) Question 2: In this way, the view in the pmainfrm access window is normal. However, when an access status bar is found: Pmainfrm-& gt; m_wndstatusbar.setpanetext (2, "test ); Debug assertion failed! (No problem in the window thread) The location is in wincore. cpp. Assert (P = pmap-& gt; lookuppermanent (m_hwnd ))! = NULL | (P = pmap-& gt; lookuptemporary (m_hwnd ))! = NULL ); Why is access to view normal? Question 3: What should I do if it is implemented through a transfer handle? I encountered a problem when using the following code: Void cmainframe: onmenu_start () { ... Hwnd = getsafehwnd (); Afxbeginthread (mythread, hwnd ); ... } Uint mythread (lpvoid pparam) { Cmainframe * pmainfrm = (cmainframe *) (cwnd: fromhandle (hwnd) pparam )); ... } Pmainfrm is obtained through the thread during execution, and access to its members is abnormal. netizen: hewwatt the general reasons are as follows: 1. most of the MFC classes are not thread-safe, and cwnd and its message routing are among the most. 2. most methods of the MFC interface class are implemented through sendmessage, and other messages are sent and processed during Message Processing . If the message processing function is not thread-safe, calling these methods from the working thread will sooner or later conflict with the user message response of your interface thread 3. cxxxx: fromhandle will look up the table based on the caller's thread. If the user-created cxxxx object cannot be found, it will create a temporary object. Because you call this method in the work thread, of course, you cannot find the object you created in the main interface thread. In this case, you create a temporary object and return it to you. You cannot expect its member variables to be meaningful. Therefore, you can only use cwnd: fromhandle to use because it only contains one m_hwnd member. However, remember Cross-thread direct or indirect call: sendmessage is usually unpredictable. 2. cause Analysis MFC interface packaging class (the asserted function call fails when multiple threads exist) date: 18:06:00 [host01.com] MFC interface packaging class -- When multithreading occurs, the member function call assertions fail. the following problems are frequently seen on the Forum: DWORD winapi threadproc (void * pdata) // thread function (for example, used to obtain data from the comport) {< br> // data retrieval cycle // put the obtained data in variable I cabcdialog * pdialog = reinterpret_cast (pdata ); assert (pdialog); // If assert_valid (pdialog) fails, pdialog-& gt; m_data = I; pdialog-& gt; UPD Atedata (false); // updatedata internal assert_valid (this) asserted failure ... }< br> bool cabcdialog: oninitdialog () {< br> cdialog: oninitdialog (); // other initialization Code createthread (null, 0, threadproc, this, 0, null); // create a thread return true; }< br> note that the assertion in the above comments fails. This article explains why the assertion fails from the underlying implementation of MFC, it also explains why the MFC should be implemented in this way and the corresponding solutions. before explaining the underlying implementation of the MFC interface packaging class, the basic knowledge of the window class should be explained as it is related to the window. Window Type A window class is a structure, and an instance of it represents a window type. It is similar to the class concept in C ++ (although its representation is completely different, the C ++ class is only the type of memory layout and Its Operation Concept. Therefore, it is called a window class. A window is a logical concept with device operation capabilities, that is, something that can operate a device (usually a display. A window is an instance of a window class, just like an instance of a class in C ++, it can have a member function (although in different forms ), however, you must specify the purpose of the window-to operate the device (this can also be seen from Microsoft's API functions for the window, mainly for the convenience of device operations ). Therefore, the window should not be used for the creation of function objects because it has the function of member functions. Although this is good, it seriously violates the semantic requirements (for semantics, refer to my other article.Article-"Semantic needs") is not advocate, but most of them are caused by the addition of the MFC interface packaging class.ProgramMembers often mix the logic into the interface. The window class is a structure, and most of the Members have little significance, but Microsoft is willing to work out it. If you do not want to use the interface API (Windows User Interface API), you can ignore those members. Only one member is important-lpfnwndproc, message processing function. The outside world (using the window code) can only use the message operation window, this is like an example of a class written in C ++ with a good object-oriented style. It can only be operated through its Public member functions. Therefore, the message processing function represents everything in a window (ignoring the role of other Members in the window class ). It is easy to find that the window instance only has member functions (message processing functions), and does not have member variables, that is, no specific memory is associated with a specific window, the window cannot be in the state (Windows still provides the window properties API to ease this situation ). This is exactly the root cause of the above problem. In order to solve the problem that Windows cannot be stateful (this is actually a flexible performance of Windows), there are many methods, while MFC can easily expand existing window classes, select a ing to bind a window handle (Unique Identifier of the window) to a memory block, this memory block is an instance of the well-known MFC interface packaging class (derived from cwnd. MFC status Status means that the instance can reproduce information across time periods through some means, A c ++ class instance is a type of class that changes the value of the member variable of an instance through the Public member function to achieve stateful effect. In MFC, there are three statuses: module status, Process status, and thread status. The status of the three instances: module, process, and thread. Because the code is run by a thread and closely related to the other two, it is also called local data. Module local data Variables With module locality. A module is a PE file loaded into the virtual memory space of the process, that is, the EXE file itself and the DLL file it loads. The module locality is the same pointer. Different Memory Spaces are accessed Based on code execution from different modules. In fact, only one global variable is declared for each module, and the previous "code" is in the MFC library file, then, the local module can be implemented through a switchover process (assigning the address of the global variable of the module to the aforementioned pointer. In MFC, this process is switched by calling afxsetmodulestate. Generally, afx_manage_state macro is used for processing. Therefore, the following common statements are used for switching the module status: Afx_manage_state (afxgetstaticmodulestate ()); A structure (afx_module_state) is defined in MFC. Its instance is module-local and records global changes at the module level, such as global application object pointers and resource handles of this module. One of the member variables is the local data of the thread and the type is afx_module_thread_state, which is the key to this article. Process local data Variables with local processes. Similar to the module locality, that is, the same Pointer Points to different Memory Spaces in different processes. This mechanism has been implemented for Windows virtual memory space. However, if the DLL supports win32s, it shares its global variables, that is, different processes load the same DLL to access the same memory. Win32s is designed for Win32-based applications to run on Windows 3.1, because Windows 3.1 is a 16-bit operating system and has long been eliminated, the current DLL model has already achieved the local nature of the process (but it can still achieve the DLL effect in win32s through the Sharing Section). Therefore, the Process status is actually a global variable. There are many structures in MFC as local data, such as _ afx_win_state, _ afx_debug_state, and _ afx_db_state, which are global variables with local processes used by MFC. Thread Local Data Variables with local thread. As shown above, different threads access different memory spaces for the same pointer. This MFC is implemented through local thread storage (TLS -- Thread Local storage, which is not used here because it is irrelevant to this Article. MFC defines a structure (_ afx_thread_state) to record some global variables at the Thread level, such as the last module status pointer and the last message. Module thread status A structure (afx_module_thread_state) defined in MFC. Its instance is thread-local and module-local. That is to say, different threads accessing the MFC library functions from the same module and thread from different modules will lead to different operations on the memory space. It is used in afx_module_state to record thread-related but module-level data. For example, the focus of this article is window handle ing. Packaging Class Object and handle ing Handle ing-chandlemap, an underlying helper class provided by MFC, which should not be directly used by programmers. It has two important member variables: cmapptrtoptr m_permanentmap, m_temporarymap ;. Record the permanent handle binding and temporary handle binding respectively. As mentioned above, MFC uses a ing to bind a window handle to an instance of its packaging class. m_permanentmap and m_temporarymap are the mappings, respectively ing permanent packaging class objects and temporary packaging class objects, in the afx_module_thread_state mentioned above, there is a member variable: chandlemap * m_pmaphwnd; (the reason is that chandlemap * uses the lazy Programming Method to save resources as much as possible) in addition, member variables such as m_pmaphdc and m_pmaphmenu are used to realize the top binding ing of HDC and hmenu respectively. Why do these mappings need to be placed in the module thread state rather than in the thread state or module state? The handle of these packaging classes is related to the thread (for example, hwnd only has the thread to create it ). to receive messages) in addition, the packaging class objects in this module may be different from the other module (for example, the packaging class is a class derived from a DLL, such as. the cabutton instance and B. if the cbbutton instance defined in DLL is in one thread at the same time. At this time, the thread uninstalls A. dll, and then the cabutton instance receives a message and processes it. A serious error will occur-the class code has been uninstalled ). The packaging class has two meanings: packaging the hwnd operation to accelerate code writing and provide the effect of window subclass (not superclass) to derive the window class. There are two types of packaging objects for threads: Permanent packaging objects (hereinafter referred to as permanent objects) and temporary packaging objects (hereinafter referred to as temporary objects ). The significance of a temporary object is only to wrap the hwnd operation to accelerate code writing, and does not have the function of deriving window classes. A permanent object has two meanings of the preceding packaging class. When creating a window (that is, in cwnd: createex), MFC processes the notification in advance (before wm_create and wm_nccreate) through the hook, afxwndproc is used to subclass the created window and add the corresponding cwnd * to the ing of the permanent object of the current thread. In afxwndproc, always by cwnd :: fromhandlepermanent (obtain the permanent object corresponding to hwnd) gets the permanent object corresponding to the window handle of the current message in the current thread, then, the windowproc member function of cwnd * is called to process messages to implement the effect of the derived window class. This means that permanent objects have the significance of window subclass, rather than simply encapsulating hwnd operations. To associate an hwnd with an existing packaging Class Object, call cwnd :: attach maps the packaging Class Object and hwnd to a permanent object (however, the permanent object obtained by this method does not necessarily have the subclass function, and is likely to be the same as the temporary object, for encapsulation purposes only ). If you want to obtain a temporary object, you can use the static member function cwnd: fromhandle to obtain it. When the object is called temporary, it is generated by the MFC internal (chandlemap: fromhandle), and its internal (chandlemap: deletetemp) is destroyed (usually through cwinthread :: onidle calls afxunlocktempmaps ). This programmer should never try to destroy the temporary object (even if the thread to which the temporary object belongs has no message loop, it cannot call cwinthread: onidle. At the end of the thread, the structure of the chandlemap still destroys the temporary object ). Cause Why are there two packaging objects? Is it fun? Note that the window model mentioned above-the message mechanism can only be used to interact with the window. Note that the window is a thread-safe instance. When writing a window, you do not need to consider that multiple threads will simultaneously access the window state. If two types of packaging objects are not used, call setprop In the hook created by the window to bind the created window handle to the corresponding cwnd, can I bind a window handle to a memory block? The derived class CA of cwnd has a member variable m_bgcolor to determine the color used to fill the background. Thread 1 creates instance a of Ca and transmits its pointer to thread 2. Thread 2 sets a. m_bgcolor to red. Obviously, CA: m_bgcolor is NOT thread-safe. If there are more than one thread 2, A. m_bgcolor will cause thread access conflicts. This seriously violates this requirement that the window is thread-safe. Because the non-message mechanism is used to interact with the window, it fails. Continue. If you give CA a public member function setbgcolor and use atomic operations to protect m_bgcolor, isn't everything normal? In Ca: onpaint, m_bgcolor is used twice for plotting. If CA: setbgcolor is called by another thread between the two plotting, CA: m_bgcolor is changed, the problem is serious. That is to say, not only do CA: m_bgcolor write operations require protection, but read operations also require protection. This is just a member variable. Then proceed. According to the definition of the window itself, only the message is used to interact with it, that is, a custom message, such as am_setbgcolor, and then sendmessage in CA: setbgcolor, modify CA: m_bgcolor in the response function. Perfect. This is a good design that conforms to the window concept, but it requires every programmer to note this when writing every packaging class, and most importantly, the concept of C ++ class does not play a role in this design, causing serious resource waste. Therefore, MFC decides to take advantage of the concept of C ++ class, so that the packaging class object looks equivalent to the window itself, so the above two packaging class objects are used. Let the packaging class object be thread-protected with different threads. That is to say, a thread cannot or should not access the packaging Class Object in another thread (because the packaging class object is equivalent to a window, this is the objective of MFC, not that the packaging class itself cannot be accessed across threads). "No" means that macro implementation is implemented through assertions in the packaging class member functions (in cwnd :: in assertvalid), and "should not" is clearly explained above. Therefore, the root cause of assertion failure at the beginning of this article is that it violates "no" and "no ". Although packaging objects cannot be accessed across threads, window handles can be accessed across threads. Because the packaging objects are not only equivalent to windows, but also change the window interaction mode (which is exactly the application of the C ++ class concept ), in this way, you do not have to use the message mechanism to interact with windows. Note that if you access a packaging Class Object across threads and use the C ++ class concept to operate on it, thread protection is required, this problem is eliminated when cross-thread access is not allowed. Therefore, the generation of temporary objects is just as mentioned above. It facilitates code writing and does not provide subclass effect, because the window handle can be accessed across threads. Solution You have understood the cause of the failure, so make the following changes: DWORD winapi threadproc (void * pdata) // thread function (for example, used to obtain data from the comport) { // Data acquisition cycle // Put the obtained data in variable I Cabcdialog * pdialog = static_cast <cabcdialog *> ( Cwnd: fromhandle (reinterpret_cast Assert_valid (pdialog); // The assert_valid may fail. Pdialog-& gt; m_data = I; // This is a poor design. For details, refer to my other article: semantic needs. Pdialog-& gt; updatedata (false); // assert_valid (this) in updatedata may fail to be asserted. ... } Bool cabcdialog: oninitdialog () { Cdialog: oninitdialog (); // Other initialization code Createthread (null, 0, threadproc, m_hwnd, 0, null); // create a thread Return true; } The reason is "possible", because the temporary object is encapsulated by the hwnd operation, not the encapsulation of the window class. Therefore, all temporary hwnd objects are cwnd instances. Even if the above forced conversion is cabcdialog *, it is still cwnd *. Therefore, when cabcdialog: assertvalid is called in assert_valid, it defines some additional checks and may find that this is a cwnd instance rather than a cabcdialog instance, leading to assertion failure. Therefore, we should replace all cabcdialog with cwnd. Although this failed, it is still wrong (not to mention pdialog-& gt; m_data) because the temporary object is the encapsulation of the hwnd operation, unfortunately, updatedata is only a dialog box data exchange mechanism (DDX) provided by MFC. It is not implemented by sending messages to hwnd, but by the virtual function mechanism. Therefore, in updatedata, calling the instance's dodataexchange cannot call cabcdialog: dodataexchange, but cwnd: dodataexchange, so nothing will happen. Therefore, a reasonable (not necessarily the best) solution is to send a message to the cabcdialog instance, and transmit data through an intermediate variable (such as a global variable) instead of using cabcdialog: m_data. Of course, if there is little data, for example, in this example, data should be transmitted as message parameters to reduce Code complexity. If there are many data, it should be passed through global variables, reduces the buffer management fee. The modification is as follows: # Define am_datanotify (wm_user + 1) Static DWORD g_data = 0; DWORD winapi threadproc (void * pdata) // thread function (for example, used to obtain data from the comport) { // Data acquisition cycle // Put the obtained data in variable I G_data = I; Cwnd * pwnd = cwnd: fromhandle (reinterpret_cast Assert_valid (pwnd); // in this example, you should directly call the platform sendmessage without calling the packaging class. Pwnd-& gt; sendmessage (am_datanotify, 0, 0 ); ... } Begin_message_map (cabcdialog, cdialog) ... On_message (am_datanotify, ondatanotify) ... End_message_map () Bool cabcdialog: oninitdialog () { Cdialog: oninitdialog (); // Other initialization code Createthread (null, 0, threadproc, m_hwnd, 0, null); // create a thread Return true; } Lresult cabcdialog: ondatanotify (wparam/* wparam */, lparam/* lparam */) { Updatedata (false ); Return 0; } Void cabcdialog: dodataexchange (cdataexchange * PDX) { Cdialog: dodataexchange (PDX ); Ddx_text (PDX, idc_edit1, g_data ); } 3. Notes What is "thread security? I often heard that the MFC object should not be used across threads, because MFC is NOT thread-safe. For example, the cwnd object should not be used across threads and can be replaced by a window handle (hwnd. The csocket/casyncsocket object should not be used across threads and should be replaced by a socket handle. So what is thread security? When should I consider it? If the program involves multiple threads, thread security should be considered. For example, if an interface is designed and needs to be used in a multi-threaded environment in the future, or an object needs to be used across threads, this must be considered. There is no authoritative definition of thread security. Here I will only talk about my understanding: the provided interfaces for threads are atomic operations or switching between multiple threads will not lead to ambiguity in the execution results of this interface, that is to say, we do not need to consider synchronization issues. Generally, "thread security" is caused by multi-thread access to shared resources. If we need to take synchronization Measures to Protect the shared resources accessed by an interface, this interface is NOT thread-safe. both MFC and stl are not thread-safe. how can we design a thread-safe class or interface? If all the data accessed in the interface belongs to private data, such an interface is thread-safe. or several interfaces are read-only operations on shared data, so these interfaces are thread-safe. if data is shared between multiple interfaces, and data is read and written, the caller does not need to consider data synchronization if the designer takes the synchronization measures, this interface is thread-safe, otherwise it is not thread-safe. What should I pay attention to in multi-thread programming? 1. Use global variables and static variables as few as possible to share data, and use parameters to pass objects as much as possible. The object passed by parameters should include only required member variables. The required member variables must be operated by multiple threads. Many people think that this pointer (which may be an object pointer) is passed as a thread parameter, so that the thread has too many operation permissions, and the parameters in this are arbitrary. The whole program is completed by one person, which may be noticed with no errors, but the program will be totally invisible as long as you turn around. When two threads operate on a member variable at the same time, the program crashes. Worse, this error is hard to reproduce. (I am depressed about this problem. We are a few people who compiled the program into a debug version. After several days of use, they found the error. Finding an error is just the beginning, because it is also very difficult to prove that the bug has been modified successfully .) In fact, data interaction between threads is mostly unidirectional. at the entrance of the thread callback function, we try to back up the incoming data to a local variable (of course, variables used for inter-thread communication cannot be processed in this way). This can be well solved by only processing local variables. 2. Use threads with caution in MFC. Because the MFC framework assumes that your message processing is completed in the main thread. First, the window handle belongs to the thread. If the thread with the window handle exits, if the other thread processes the window handle, the system will encounter a problem. To avoid this situation, MFC keeps making assert errors when calling the message (window) processing function in the Child thread. A typical example is csocket. Because csocket uses a hidden window to implement false blocking, it is inevitable to use a message processing function. If you use csocket in a child thread, you may see the assert pop-up. 3. Do not register COM components in different threads at the same time. Two threads, one registering 1.ocx, 2.ocx, 3.ocx, 4.ocx; and the other registering 5.ocx, 6.ocx, 7.ocx, 8.ocx. the result is a deadlock, which occurs in freelibrary and dllregisterserver, respectively, the eight OCX files are made in MFC and may also contain MFC bugs. However, the dllregisterserver is stuck in getmodulefilename, while getmodulefilename is an API! If you see the cause, please kindly advise me. 4. Do not make the thread so complicated. Many beginners hate to use all the functions related to the thread. They are mutually exclusive here. They wait, start the thread later, and shut down the thread later, which is not inferior to the GOTO statement. A good multi-threaded program should use as few threads as possible. How can this sentence be understood? That is to say, we should try to unify a data shared zone to store data queues, and the worker thread will retrieve data from the queue, process the data, and then put the data back. This will be modularized, object processing. Instead of processing every data with a sub-thread, it is closed after processing is complete. Although it is directly written, it will become tired after maintenance. From: http://hi.baidu.com/jumbo/blog/item/fd5d70f0e25130a7a40f52cd.html |