Use the critical section to implement the optimized process synchronization object - principle and implementation

zhaozj2021-02-08  305

Use the critical section to implement the optimized process synchronization object - principle and implementation

By jeffrey.richter

VcBear's enthusiasm

Realize your own synchronization object? 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 can't affirm the water?

1 Overview:

In a multi-process environment, you need to synchronize threads. Common synchronization objects have critical sections, mutex, Semaphore, events (event), except for the critical paragraph, Kernel objects.

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 document has explained that the critical section cannot be across the process, that is, the critical segment cannot be synchronized with threads between multiple processes, and can only be used for threads inside the 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, you still have no job.

Is there any way to synchronize the thread across the process?

2. Principle and implementation

2.1 Why is the critical section? Is it "really" fast?

Indeed, the critical paragraph is faster than other core state synchronization objects, because both functions of EntercriticalSection and LeaveCriticalSECTION get a lot of benefits from the InterlocKedxxx series function (the code below demonstrates how the critical section uses the interlockedxxx function). The InterlocKedxxx series function is completely running in a user-state space, which does not need to be from the user to the core state.

Switching between. 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 the process border?

What does it mean to "the critical paragraph equivalent to an EVENT core state"?

Look at the definition of the critical section

TypedEf struct _rtl_critical_section {

PRTL_CRITICAL_SECTION_DEBUG Debuginfo;

//

// the folowing three fields control entering and exiting the critical

// section for the resource

//

Long lockcount;

Long Recursioncount;

Handle Owningthread; // from the thread's clientid-> uniqueethread

Handle Locksemaphore;

DWORD SpinCount;

} RTL_CRITICAL_SECTION, * PRTL_CRITICAL_SECTION;

#typedef rtl_critical_section criticl_section

In the Critical_SECTION data structure, there is a handle of an EVENT kernel object (the Undocument member LockSemaphore, which is included, is an EVENT handle, not a semaphore). 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 existing in 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 synchronization object class

Jeffrey Richter has written his own critical paragraph, now he has improved his critical paragraph and package 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 the hard brain. See the principle of optimization: enter the core state when needed. Otherwise, synchronize in the user state)

The following is a 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 overce

///

Class 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 pszname, dword dwspincount) {

CommonConstructor (PVOID) Pszname, True, DWSPINCOUNT

}

Optex.cpp

/ ************************************************** ****************************

Module Name: OPTEX.cpp

Written by: Jeffrey Richter

Purpose: Implements the Coptex (Optimized Mutex) Synchronization Object

*********************************************************** *************************** /

#include

#include "optex.h"

///

Bool Coptex :: CommonConstructor (Pvoid ​​Pszname, Bool Funicode, DWORD DWSPINCOUNT)

{

m_hevt = m_hfm = NULL;

m_psharedInfo = null;

System_info sinf;

GetSystemInfo (& SINF);

m_funiprocessorhost = (sinf.dwnumberofprocessors == 1);

Char sznamea [100];

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

IF (m_psharedInfo-> m_dwthreadid! = 0) debugbreak ();

#ENDIF

UnmapViewoffile (m_psharedInfo);

CloseHandle (M_HFM);

CloseHandle (M_HEVT);

}

///

Void Coptex :: SetSpincount (DWORD DWSPINCOUNT) {

IF (! m_funiprocessorhost)

InterlockedExchange ((plong) & m_psharedInfo-> m_dwspincount, dwspincount;

}

///

Void Coptex :: ENTER () {

// spin, Trying to get the optex

IF (TryEnter ()) Return;

DWORD DWTHREADID = GetCurrentThreadId (); // The calling thread's id

IF (InterlockedIncreme "== 1) {

// Optex is unned, 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 overce

}

}

}

///

Bool Coptex :: Tryner () {DWORD DWTHREADID = GetCurrentThreadId (); // The calling thread's id

// if the lock count is Zero, The Optex is unned 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 Own IT

m_psharedInfo-> m_lrecursecount = 1; // We own it overce

} 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) {

// West Own the optex

InterlockedDecrement (& m_psharedInfo-> m_llockcount);

} else {

// We don't @ THE OPTEX

InterlockedExchange ((plong) & m_psharedInfo-> m_dwthreadid, 0);

IF (INTERLOCKEDDECREMENT (& m_psharedInfo-> m_llockcount)> 0) {

// Other Threads Are Waiting, Wake ONE OF THEM

SetEvent (m_hevt);

}

}

}

/ END OF FILE /

Using this Coptex is a very simple thing, as long as constructs the following two configuration functions, a C class is used. Constructor

Coptex (LPCSTR PSZNAME, DWORD DWSPINCOUNT = 4000);

Coptex (lpcwstr pszname, dword dwspincount = 4000);

They all call

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. The constructor supports ANSI or Unicode's name.

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 fail, the resulting handle is NULL.

Similarly, you can get a handle of a shared memory image file.

After the configuration is successful, when you need to synchronize, the corresponding process synchronization operation is performed simultaneously according to the situation. 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. Just use the critical paragraph when it is

Other functions and Win32 functions of the CopTex class are one or one. The programmers who are familiar with the synchronization object should be easy to understand.

How does Coptex work? In fact, a CopTex contains two data blocks: a local, private; the other is a global, shared. A CopTex object constructor, the local data block contains Coptex Member variable: m_hevt variable is initialized to a naming event object handle; M_HFM variable is initialized to a memory image file object handle. Since the object represented by these handles is named, they can share between processes. Note that "objects" can share, not "object handles". The CopTex object in each process must keep these handles in this process.

M_PSHAREINF member points to a memory image file, the global data block exists in the memory image file. The SharedInfo structure is an organization of memory image data, which defines the structure in the CopTex class, and critcial_section structure very similar.

Typedef struct {

DWORD M_DWSPINCOUNT;

Long m_llockcount;

DWORD M_DWTHREADID;

Long m_lrecursecount; Report-2001-03-07.htm

SharedInfo, * psharedinfo;

m_dwspincount: Spin count

M_llockcount: Lock count

M_dWthreadID: Thread ID with this critical section

M_LRECURSECount: This thread has the count of the critical section

Ok, take a closer look, master's style. Note that when you sync, regarding whether the same thread, a series of judgments about the value of LockCount, and the use of the InterlocKedXxx series function.

Bear like this code is the code, it is simple, the ideas are clear, the principle is value, after reading it, just want to drink a sound "and learn again, cool!"

BEAR also writes tired, collecting :). 2001.3.2

As soon as I renew the name of Jeffrey, there is a BEAR: D

Translation is wrong, please find vcbear@sina.com or message, do not understand Win32 programming below:

HAVE A Question About Programming in Win32? Contact Jeffrey Richter At http://www.jeffreyrichter.com/from The January 1998 Issue of Microsoft Systems Journals Japan.

转载请注明原文地址:https://www.9cbs.com/read-1730.html

New Post(0)