Overview:
Non-kernel object critical zones are very suitable for serializing access to data in a process because they are fast. However, we may want to synchronize some applications with other special events on the computer or operations executed in other processes. At this time, the critical section is powerless. You need to use the kernel object for synchronization.
The following kernel objects can be used to synchronize threads:
1. process, Processes
2. Thread, threads
3. Files, files
4. console input, console input
5. file change notifications
6. mutex, mutexes
7. semaphores, semaphores
8. Events (automatic resetting events and manual resetting events), events
9. Optional timers (only used for window NT4 or higher), waitable timers
10. Jobs
Each of the above objects can be in one of two States: signaled and nonsignaled ).Available means there is a signal, and occupied means there is no signal. For example, when processes and threads terminate, their kernel objects become signal-free, while they are in the creation and running status.
Kernel Object synchronization application:
1. When a thread obtains the kernel object handle of a process, it can change the process priority, obtain the exit code of the process, and synchronize the thread with the end of a process.
2. When obtaining the kernel object handle of a thread, you can: Change the running status of the thread, and synchronize with the end of the thread.
3. When obtaining the file handle, this thread can synchronize with the I/O operations of an asynchronous file.
4. the console input object can be used to wake the thread up when the input enters to execute related tasks.
5. Other kernel objects-file change notifications, mutex volumes, semaphores, events, and other timers-only exist for synchronization objects. Correspondingly, there are Win32 functions to create, open, and close these objects and synchronize threads with these objects.
Unique features of mutex:Mutex, that is, mutex, the meaning of the synchronization element. When two or more threads need to access a shared resource at the same time, the system needs to use a synchronization mechanism to ensure that only one thread uses the resource at a time. Mutex only grants an exclusive access to shared resources to one thread. If a thread obtains the mutex, the second thread to obtain the mutex will be suspended until the first thread releases the mutex.
The difference between a mutex object and all other kernel objects is that it is owned by a thread.All other synchronization objects either have signals or have no signals. In addition to recording the current signal status, the mutex object must remember that the thread owns it at this time. If a thread is terminated after obtaining a mutex object (that is, it is set to a non-signal state), the mutex will be discarded. In this case, the mutex will always remain stateless, because no other thread can release it by calling releasemutex.
When the system finds this situation, the mutex is automatically set back to the signal state. Other threads waiting for the semaphore will be awakened, but the return value of the function is wait_abandoned rather than normal wait_object_0. At this time, other threads can know whether the mutex is released normally.
Others, the mutex is similar to critical_section. A thread with this mutex will return a successful result immediately when waitforsingleobject is called each time, But the use count of the mutex will increase. Similarly, releasemutex will be called multiple times to make the reference count zero, can be used by other threads.
Several questions:
Q: Will the system reset the status of other kernel objects when the ownership is not released after the thread ends abnormally? If it is reset, there will be no mark, and it will be no different from normal release, that is, it will not have the mutex, this will return wait_abandoned?
Note:The thread has a certain kernel object.AndThe thread has the ownership of a kernel object.,The two are different.. When a thread has a certain kernel object, it should be emphasized that when the thread ends, if the thread has the right to access the kernel object, the kernel object will also be discarded, because it cannot reset its signal status, and the thread has the permission to use a certain kernel object, it means that the thread can call certain functions to access this kernel object or perform some operations on this kernel object]
1: mutex)
Functions used to maintain thread synchronization with mutex kernel objects include createmutex (), openmutex (), releasemutex (), waitforsingleobject (), and waitformultipleobjects. Before using a mutex object, you must first create or open a mutex object through createmutex () or openmutex. The createmutex () function is prototype:
Handle createmutex (
Lpsecurity_attributes lpmutexattributes, // Security Attribute pointer
Bool binitialowner, // initial owner
Lptstr lpname // mutex object name
); The binitialowner parameter is used to control the initial state of the mutex object. Generally, it is set to false to indicate that the mutex object is not occupied by any thread during creation. If the object name is specified when a mutex object is created, you can obtain the handle of the mutex object elsewhere in the process or through the openmutex () function of other processes. The prototype of the openmutex () function is handle openmutex (
DWORD dwdesiredaccess, // access flag
Bool binherithandle, // inheritance flag
Lptstr lpname // mutex object name
); When a thread that has access to the resource no longer needs to access the resource and needs to leave, it must release its mutex object through the releasemutex () function. Its function prototype is: bool releasemutex (handle hmutex); its unique parameter hmutex is the handle of the mutex object to be released. As for waitforsingleobject () and waitformultipleobjects (), the functions of the wait function in the thread synchronization of mutex objects are basically the same as those in other kernel objects, and they are also waiting for notifications of mutex kernel objects. However, it should be pointed out that the return value of the wait function is no longer the normal wait_object_0 (for the waitforsingleobject () function) when the mutex object notification causes the call to wait for the function to return) or a value between wait_object_0 and wait_object_0 + nCount-1 (for the waitformultipleobjects () function) will return a wait_abandoned_0 (for the waitforsingleobject () function) or a value between wait_abandoned_0 and wait_abandoned_0 + nCount-1 (for the waitformultipleobjects () function ). This indicates that the mutex object that the thread is waiting for is owned by another thread, but this thread has been terminated before sharing resources are used. In addition, the method of using mutex objects is different in the way of waiting for the thread's schedulability. When other kernel objects are not notified, by calling the wait function, the thread will be suspended and out of scheduling, while the mutex method can still be schedulable while waiting, this is one of the Unconventional Operations that mutex objects can perform. During programming, mutex objects are mostly used to protect the memory blocks accessed by multiple threads, it ensures that any thread has reliable and exclusive access to this memory block when processing it.
2: semaphores)
The method for synchronizing semaphore objects to threads is different from the previous methods. Signals allow multiple threads to use shared resources at the same time, which is the same as PV operations in the operating system. It specifies the maximum number of threads simultaneously accessing shared resources. It allows multiple threads to access the same resource at the same time, but it needs to limit the maximum number of threads that can access the resource at the same time. When using createsemaphore () to create a semaphore, you must specify the maximum allowed resource count and the current available resource count. Generally, the current available resource count is set to the maximum Resource Count. Each time a thread is added to access a shared resource, the current available resource count is reduced by 1, as long as the current available resource count is greater than 0, a semaphore signal can be sent. However, when the current available count is reduced to 0, it indicates that the number of threads currently occupying resources has reached the maximum allowed number, and other threads cannot enter, at this time, the semaphore signal cannot be sent. After processing shared resources, the thread should use the releasesemaphore () function to increase the number of currently available resources by 1 while leaving. The current available resource count cannot exceed the maximum resource count at any time. Semaphores control thread access resources by counting. In fact, semaphores are also called Dijkstra counters. Functions such as createsemaphore (), opensemaphore (), releasesemaphore (), waitforsingleobject (), and waitformultipleobjects () are used for thread synchronization. Createsemaphore () is used to create a semaphore kernel object. Its function prototype is handle createsemaphore (
Lpsecurity_attributes l1_maphoreattributes, // Security Attribute pointer
Long linitialcount, // initial count
Long lmaximumcount, // maximum count
Lptstr lpname // Object Name Pointer
); The lmaximumcount parameter is a signed 32-bit value that defines the maximum allowed Resource Count. The maximum value cannot exceed 4294967295. The lpname parameter can define a name for the created semaphore. because it creates a kernel object, this semaphore can be obtained through this name in other processes. The opensemaphore () function can be used to open the semaphore created in other processes based on the semaphore name. The function prototype is as follows: handle opensemaphore (
DWORD dwdesiredaccess, // access flag
Bool binherithandle, // inheritance flag
Lptstr lpname // semaphore name
); When the thread leaves processing shared resources, it must use releasesemaphore () to increase the current available resource count. Otherwise, the actual number of threads currently processing shared resources does not reach the value to be limited, but other threads still cannot enter because the current number of available resources is 0. Releasesemaphore () function prototype: bool releasesemaphore (
Handle hsemaphore, // semaphore handle
Long lreleasecount, // increase the count
Lplong lppreviouscount // previous count
); This function adds the value in lreleasecount to the current resource count of the semaphore. Generally, this function sets lreleasecount to 1. You can also set other values if needed. Waitforsingleobject () and waitformultipleobjects () are mainly used at the entrance of the thread function trying to enter the shared resource. They are mainly used to determine whether the current available resource count of the semaphore allows the thread to enter. Only when the current available resource count is greater than 0 will the monitored semaphore kernel object be notified. The usage of semaphores makes it more suitable for synchronization of threads in socket (socket) programs. For example, if the HTTP server on the network needs to limit the number of users who access the same page at the same time, you can set a thread for none of the users to request the page on the server, the page is the shared resource to be protected. By using semaphores to synchronize threads, users can access a page no matter how many users at any time, only threads with the maximum number of users can access this page, while other access attempts are suspended. This page can only be accessed after a user exits.
3: Event)
Event objects can also be synchronized by means of notification operations. In addition, threads in different processes can be synchronized. The semaphore contains several operation primitives: createevent () to create a semaphore
Openevent () opens an event
Setevent () reset event
Waitforsingleobject () waits for an event
Waitformultipleobjects () waits for multiple events. When using the critical section, you can only synchronize threads in the same process. When using the event kernel object, you can synchronize threads outside the process, the premise is to obtain access to this event object. It can be obtained through the openevent () function. Its prototype is: handle openevent (
DWORD dwdesiredaccess, // access flag
Bool binherithandle, // inheritance flag
Lptstr lpname // pointer to the event object name
); If the event object has been created (you must specify the event name when creating the event), the function returns the handle of the specified event. For event kernel objects that do not specify the event name when creating an event, you can call createevent () by using the inheritance of the kernel object or calling the duplicatehandle () function () to obtain access to the specified event object. The synchronization operations performed after the access permission is obtained are the same as those performed in the same process. If you need to wait for multiple events in a thread, use waitformultipleobjects () to wait. Waitformultipleobjects () is similar to waitforsingleobject (), and monitors all handles in the handle array. The handles of these monitored objects have equal priority, and neither of them has higher priority than other handles. The function prototype of waitformultipleobjects () is:
DWORD waitformultipleobjects (
DWORD ncount, // number of pending handles
Const handle * lphandles, // The first address of the handle Array
Bool fwaitall, // wait sign
DWORD dwmilliseconds // wait time interval
); The ncount parameter specifies the number of kernel objects to wait, and the array of these kernel objects is pointed by lphandles. Fwaitall specifies the two waiting methods for the specified ncount kernel object. If it is true, the function returns only when all objects are notified, if this parameter is set to false, only one of them is notified. Dwmilliseconds serves exactly the same purpose as waitforsingleobject. If the wait times out, the function returns wait_timeout. If a value from wait_object_0 to wait_object_0 + is returned in the nCount-1, it means that the status of all specified objects is notified (when fwaitall is true) or the index of the object to be notified after wait_object_0 is subtracted (when fwaitall is false ). If the returned value is between wait_abandoned_0 and wait_abandoned_0 + nCount-1, it indicates that the status of all specified objects is notified, and at least one of the objects is the discarded mutex object (when fwaitall is true ), or subtract wait_object_0 to indicate the index of a mutex object waiting for normal termination (when fwaitall is false ).
Conclusion ::
1: threads waiting for operation
The thread mainly uses two functions to set itself as sleep to wait for the kernel object to becomeSignal available: Both functions are blocking functions.
DWORD waitforsingleobject (
Handle hhandle,
DWORD dwmilliseconds
);
DWORD waitformultipleobjects (
DWORD ncount,
ConstHandle * lphandles, // handle Array
Bool bwaitall,
DWORD dwmilliseconds
);
Waitforsingleobject: wait for a kernel object to become a signal within a specified time (dwmilliseconds). During this time, if the waiting kernel object remains unresponsive, the calling thread will sleep, otherwise, continue. After this time is exceeded, the thread continues to run. Function return values include wait_object_0, wait_timeout, wait_abandoned (only when the kernel object is mutex), and wait_failed.
Waitformultipleobjects is similar to waitforsingleobject, But it either waits for several objects (determined by ncount) in the specified list (specified by lphandles) to change to a signal, or waits for a list (specified by lphandles) an object in becomes a signal (determined by bwaitall ).
Waitforsingleobject and waitformultipleobjects functions have important side effects on specific kernel objects, that is, theyThe kernel object determines whether to change the Signal Status of the kernel object.And execute this change. these side effects determine whether to wake up or wake up one of the processes or threads waiting for the kernel object.
(1) For process and thread kernel objects, these two functions have no side effects.
That is, after a process or thread kernel object changes to a signal, they will maintain a signal, and these two functions will not try to change the Signal Status of the kernel object. In this way, all threads waiting for these kernel objects will be awakened.
(2) For timer objects that are mutually exclusive, automatically reset events, and automatically reset, the two functions change their statuses to no signal.
In other words, once these objects become signal-free and a thread is awakened, the objects are reset to the signal-free state. As a result, only one waiting thread wakes up, and other waiting threads continue to sleep.
(3)
Waitformultipleobjects
Functions also have a very important feature.: When the bwaitall passed when it is called is true, before all the waiting objects become signaled, any waiting kernel objects that can be changed are not reset to the stateless state. In other words,
Input parameters
Bwaitall
True
, Waitformultipleobjects
Unless all specified objects can be obtained
It does not obtain the ownership of a single object (it cannot obtain the ownership, and naturally it does not change the Signal status of this object ). This is to prevent deadlocks. In other words, when bwaitall is true, waitformultipleobjects will not change the signal status of a kernel object that can be changed without obtaining all the ownership of the object, any thread waiting in the same way will not be awakened, but the thread waiting in other ways will be awakened.
2: Comparative Analysis
(1) The mutex function is very similar to that of the critical zone, but the mutex can be named, that is, it can be used across processes. Therefore, creating mutex requires more resources. Therefore, if you only use it within a process, using the critical section will bring speed advantages and reduce resource occupation. Because the mutex is a cross-process mutex, once created, it can be opened by name.
(2) mutex, semaphore, and event can all be used by the process to synchronize data. Other objects have nothing to do with data synchronization, but for the process and thread, if the process and thread are in the running status, there is no signal, and there is a signal after exiting. Therefore, you can use waitforsingleobject to wait for the process and thread to exit.
(3) The mutex can be used to specify the mode in which resources are exclusive. However, if the following problem occurs, for example, if a user buys a database system with three concurrent access licenses, the user can decide how many threads/processes can perform database operations at the same time based on the number of access licenses purchased by the user, at this time, if the mutex is used, there is no way to complete this requirement. The traffic signal object can be said to be a resource counter.