Using the principle of the critical segment to achieve an optimized process synchronous object - principle and implementation
By jeffrey.richter
VcBear's enthusiasm
Realize your own synchronization object? Do you need? Don't you need it? ... just study it with you. Forget it, I am just a guy who loves the water, I haven't written code for a long time, I have nothing to do, I will not be able to irrigate? ? 1. Overview: In the multi-process environment, you need to synchronize threads. Common synchronization objects have critical sections, mutex, Semaphore, event, etc. The critical section is a kernel object. In synchronous techniques, the critical section is the easiest to master, and, compared to the way to achieve synchronization by waiting and releaseing kernel muters, the critical section is obvious. But the critical section has a defect, The WIN32 documentation has explained that the critical section cannot be cross-processes, that is, the critical segment cannot be synchronized with threads between multiple processes, can only be used for threads within a single process. Because the critical paragraph is just a very simple data structure, It is invalid in the process space of other processes. Even if you put it in a memory image file that can be shared multi-process, it is still unable to work. Is there any way to synchronize across the process of implementation threads? 2. Principles and implementation 2.1 Why is the critical paragraph fast? Yes " Really "Fast? Indeed, the critical paragraph is faster than other core state synchronization objects, because both functions of EntercriticalSection and LeaveCriticalSECTION have a lot of benefits from the InterlocKedxxx series function (the following code demonstrates how to use InterlocKedxxx Functional). The InterlocKedxxx series function is completely running in user-state space, and does not require switching from the user to the core state. Therefore, entering and leaving a critical segment typically only requires only about 10 CPU execution instructions. When the function of the WaitForsingleObject is called, the thread is mandated between the user and the core state because the kernel object is used. On the X86 processor, this transformation generally requires 600 CPU instructions. Seeing the huge gap in this. Say, is it true "fast"? In fact, the critical section is only fast when the shared resource is not conflict. When a thread attempts to enter the critical segment that is being owned by another thread, the critical section is equivalent to an EVENT core state object, the same needs to consume about 600 CPU instructions. In fact, because such competitive situation is very small, it is very small (unless it is human), so the use of the critical section is not involved in the kernel synchronization in most of the time (when there is no competition conflict). It is high-speed, only 10 CPUs are required. (BEAR said: understand, purely play probability, MS small tricks) 2.3 What should I do if I do? What does it mean to "the critical paragraph equivalent to an EVENT core state"? See the definition of a critical first segment structure typedef struct _RTL_CRITICAL_SECTION {PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // // The following three fields control entering and exiting the critical // section for the resource // LONG LockCount; LONG RecursionCount; HANDLE OwningThread; // from the thread's ClientId-> UniqueThread hANDLE LockSemaphore; DWORD SpinCount;} RTL_CRITICAL_SECTION, * PRTL_CRITICAL_SECTION; #typedef RTL_CRITICAL_SECTION CRITICL_SECTION in CRITICAL_SECTION data structure, has a handle (structure member of the undocument Event LockSemaphore a kernel object, comprising the actual event of a Handle, not a semaphor).
As we know, the kernel object is a system globally, but the handle is all processes, not the system globally. So, even if you put a critical segment structure directly in the shared memory image, the critical segment cannot work, because the handle value of the LockSemaphore is only valid for one process, which is meaningless for other processes. In the general process synchronization, the process is to use an EVENT object created by another process, and the OpenEvent or CreaEtevent function must be called to get the handle value that the process can use. The other variables in the critical_section structure are elements dependent on the critical segment, and the MS also "Warning" programmer does not change the value of the variable in the structure. How is it realized? Look at the next step. 2.4 Coptex, optimized synchronous object class Jeffrey Richter once wrote a border section, now, he has improved his critical section and put it into a Coptex class. Member function Tryenter has the function of the function TryEntercriticalSection in NT4. This function attempts to enter the critical segment. If the failure returns, it will not hang the thread and support the SPIN count. This feature is implemented in the NT4 / SP3 by InitializeCriticalSectionAnspinCount and SetCriticalSectionsPinCount. The SPIN count is useful in the case of multiprocessor systems and high-competition conflicts. Before entering the WaitForxx core state, the critical segment performs multiple TryEnterCtriticalSecions according to the set SPIN count, and then blocking. Think about it, TryEntercriticalSection uses 10 or so, if the conflict disappears before the SPIN count is consumed, the critical paragraph object is idle, then use 10 CPU cycles to enter the critical section, do not switch to Core state. (Bear said: In order to avoid this "core state", MS himself is also a hard work. See it, the principle of optimization: enter the core state when needed. Otherwise, synchronization in the user state) The following is CopTex code. Original code download
Figure 2: Coptex Optex.h / **************************************************** ******************************************** MODULE NAME: OPTEX.H Written by: Jeffrey Richter Purpose: Defines THE COPTEX (Optimized Mutex) Synchronization Object ******************************************* ******************************************************** / #Pragma ONCE / / / COPTEX {public: Coptex ( LPCSTR pszName, DWORD dwSpinCount = 4000); COptex (LPCWSTR pszName, DWORD dwSpinCount = 4000); ~ COptex (); void SetSpinCount (DWORD dwSpinCount); void Enter (); BOOL TryEnter (); void Leave (); private: typedef struct {DWORD m_dwSpinCount; long m_lLockCount; DWORD m_dwThreadId; long m_lRecurseCount;} SHAREDINFO, * PSHAREDINFO; BOOL m_fUniprocessorHost; HANDLE m_hevt; HANDLE m_hfm; PSHAREDINFO m_pSharedInfo; private: BOOL CommonConstructor (PVOID pszName, BOOL fUnicode, DWORD dwSpinCount);}; / // Inline Coptex :: Coptex (LPCSTR PSZNAME, DWORD DWSPINCOUNT) {CommonConstructor ((pvoid) pszname, false, dwspincount;} /// Inline Coptex :: CopTex (lpcwstr pszn AME, DWORD DWSPINCOUNT) {CommonConstructor ((pvoid) pszname, true, dwspincount;} OPTEX.cpp / ************************************** *********************************************************** * Module name: optex.cpp written by: Jeffrey Richter purpose: importimized mutex (Optimized Mutex) synchronization object *************************************** *********************************************************** * / #include
IF (Funicode) {// Convert Unicode Name To ANSI WSPrintfa (Sznamea, "% S", PSZNAME); pszname = (pvoid) sznamea;} char SZ [100]; WSPrintfa (SZ, "JMR_OPTEX_EVENT_% S", PSZNAME ; m_hevt = CreateEventA (NULL, FALSE, FALSE, sz); if (! m_hevt = NULL) {wsprintfA (sz, "JMR_Optex_MMF_% s", pszName); m_hfm = CreateFileMappingA (NULL, NULL, PAGE_READWRITE, 0, sizeof (* m_pSharedInfo), sz); if (m_hfm = NULL) {m_pSharedInfo = (PSHAREDINFO) MapViewOfFile (m_hfm, FILE_MAP_WRITE, 0, 0, 0); // Note:! SHAREDINFO's m_lLockCount, m_dwThreadId, and m_lRecurseCount // members need to be initialized to 0. fortunately, a new pagefile // MMF sets all of its data to 0 when created This saves us from // some thread synchronization work if (m_pSharedInfo = NULL!) SetSpinCount (dwSpinCount);..}} return ((m_hevt ! = Null) && (m_hfm! = Null) && (m_psharedInfo! = Null);} /// Coptex :: ~ Coptex () {#ifdef _debug f (m_psharedInfo-> m_dwthreadid! = 0) debugbreak (); # Endif unmapViewoffile (m_psharedInfo); CloseHandle (M_HFM); Clos eHandle (m_hevt);} /// void COptex :: SetSpinCount (DWORD dwSpinCount) {if InterlockedExchange ((PLONG) & m_pSharedInfo-> m_dwSpinCount, dwSpinCount) (m_fUniprocessorHost!);} /// void COptex :: Enter () {/ / Spin, trying to get the Optex if (TryEnter ()) return; DWORD dwThreadId = GetCurrentThreadId (); // The calling thread's ID if (InterlockedIncrement (& m_pSharedInfo-> m_lLockCount) == 1) {// Optex is unowned, let this thread own it once InterlockedExchange ((PLONG) & m_pSharedInfo-> m_dwThreadId, dwThreadId); m_pSharedInfo-> m_lRecurseCount = 1;} else {// Optex is owned by a thread if (m_pSharedInfo->
m_dwThreadId == dwThreadId) {// Optex is owned by this thread, own it again m_pSharedInfo-> m_lRecurseCount ;} else {// Optex is owned by another thread // Wait for the Owning thread to release the Optex WaitForSingleObject (m_hevt, INFINITE ); // We got ownership of the Optex InterlockedExchange ((PLONG) & m_pSharedInfo-> m_dwThreadId, dwThreadId); // We own it now m_pSharedInfo-> m_lRecurseCount = 1; // We own it once}}} /// BOOL COptex :: TryEnter () {DWORD dwThreadId = GetCurrentThreadId (); // The calling thread's ID // If the lock count is zero, the Optex is unowned and // this thread can become the owner of it now BOOL fThisThreadOwnsTheOptex = FALSE.; DWORD dwSpinCount = m_pSharedInfo-> m_dwSpinCount; do {fThisThreadOwnsTheOptex = (0 == (DWORD) InterlockedCompareExchange ((PVOID *) & m_pSharedInfo-> m_lLockCount, (PVOID) 1, (PVOID) 0)); if (fThisThreadOwnsTheOptex) {// We Now Own The Optex InterlockedExchange ((Plong) & m_psharedInfo-> m_dwthreadid, dwthreadid; // We ing it m_ps haredInfo-> m_lRecurseCount = 1; // We own it once} else {// Some thread owns the Optex if (m_pSharedInfo-> m_dwThreadId == dwThreadId) {// We already own the Optex InterlockedIncrement (& m_pSharedInfo-> m_lLockCount); m_pSharedInfo -> m_lRecurseCount ; // We own it again fThisThreadOwnsTheOptex = TRUE; // Return that we own the Optex}}} while (fThisThreadOwnsTheOptex && (dwSpinCount--> 0)!); // Return whether or not this thread owns the Optex Return (fTHISTHREADOWNSTHEOPTEX);} /// Void Coptex :: Leave () {#ifdef _debug if (m_psharedInfo-> m_dwthreadid! = getCurrentThreadID ()) debugbreak ();
#endif if (--m_pSharedInfo-> m_lRecurseCount> 0) {// We still own the Optex InterlockedDecrement (& m_pSharedInfo-> m_lLockCount);} else {// We do not own the Optex InterlockedExchange ((PLONG) & m_pSharedInfo-> m_dwThreadId , 0); IF (INTERLOCKEDDECREMENT> 0) {// Other Threads Area Waiting, Wake One of Theme SetEvent (M_HEVT);}}} / end of file / use this Coptex is a very simple thing. As long as configured with the following two constructors instance a C class constructor can COptex (LPCSTR pszName, DWORD dwSpinCount = 4000);. COptex (LPCWSTR pszName, DWORD dwSpinCount = 4000); they are called BOOL CommonConstructor (PVOID pszName Bool Funicode, DWORD DWSPINCOUNT; Constructing a Coptex object must give it a string type name, which is necessary when breaking through the process boundary, only this name can provide shared access. Construction function supports the name of ANSI or Unicode. When another process uses the same name to construct a CopTex object, the constructor finds the existing CopTex object. In the CommonStructor code, use createEvent to create a name Event object if the name of this name already exists, then the object is The handle, and getLastError can get error_already_exists. If there is no existence, create one. If you create a failure, the resulting handle is null. The same, you can get a handle of a shared memory image file. After the construction is successful, when you need synchronization , According to the situation, simply perform the synchronization operation between the corresponding processes. The second parameter of the constructor is used to specify the SPIN count. The default is 4000 (this is the number of functions used by the operating system serialization Heap. The operating system is assigned and releases the memory, which is the pile of processes. It is also necessary to use the critical paragraph) The other functions of the Coptex class and the Win32 function are one or one. The programmers who are familiar with the synchronous object should be easy to understand. How does Coptex work? In fact, a CopTex contains two blocks (Data Blocks): A local, private; another is a global, shared. After a CopTex object constructor, the local data block contains a member variable of CopTex: m_hevt variable initialized to a naming event object handle; M_HFM variable initialization For a memory image file object handle. Since these handles represented objects are named, they can share between processes.
Note that "object" can share, not "object handle". All CopTex objects in each process must keep the value of these handles in this process. M_PShareInf member points to a memory image file, global data block in this In the memory image file, there is a specified shared name. The SharedInfo structure is an organization mode of memory image data, which is defined in the CopTex class, and the structure of critcial_section is very similar. TypedEf struct {dword m_dwspincount; long m_lockcount; dword m_dwthreadID Long m_lrecursecount;} sharedinfo, * psharedinfo; m_dwspincount: Spin count m_llockcount: Lock count M_DWTHREADID: Thread ID M_LRECURSECUNT with this critical section: This thread has the count of the critical section, take a closer look, master style. Note that when you synchronize, regarding whether the same thread, a series of judgments about the value of LockCount, and the use of the InterlocKedxxx series function, specific use of the code, the code is like this, simple, clear, ideas, principle Value, after reading it, I just want to drink a little "and learn, cool!" BEAR also wrote tired, collecting :). 2001.3.2 random reprint, as long as you don't get rid of Jeffrey, there is BEAR: D explains Please find vcbear@sina.com or leave a message, but not the Win32 programming see the following: Have a question about programming in Win32 Contact Jeffrey Richter at http://www.jeffreyrichter.com/ From the January 1998 issue of Microsoft Systems Journal?.