What is the purpose of SignalObjectAndWait regarding SetEvent and WaitForSingleObject?

I just realized that SignalObjectAndWait API function for the Windows platform. But there is already SetEvent and WaitForSingleObject . You can use them together to achieve the same goal as SignalObjectAndWait .

Based on MSDN , SignalObjectAndWait more efficient than separate calls to SetEvent and WaitForSingleObject . It also says:

The thread can use the SignalObjectAndWait function to ensure that the worker thread is in a wait state before signaling the object.

I do not fully understand this proposal, but it seems that efficiency is not the only reason why we need SignalObjectAndWait . Can someone provide a scenario in which SetEvent + WaitForSingleObject cannot provide the functions that SignalObjectAndWait offers?

+6
source share
3 answers

I understand that this single function is more efficient in that it avoids the following scenario.

The SignalObjectAndWait function provides a more efficient way of passing one object and then waiting for another compared to individual function calls such as SetEvent and then WaitForSingleObject .

When you are SetEvent and another [esp. higher priority] a queue is waiting for this event, it may happen that the thread scheduler takes control away from the signal thread. When the thread gets control back, all it does is the next call to WaitForSingleObject , thus losing the context switch for such a tiny thing.

Using SignalObjectAndWait , you hint at the kernel by saying, “hey, I’m still waiting for another event, so if that matters to you, you don’t give up context switches back and forth too much.”

+2
source

The goal, as MSDN explains, is to make sure that the thread is idle before the event is signaled. If you call WaitForSingleObject, the thread is in waitstate, but you cannot call it before calling SetEvent, since it will only call SetEvent after the wait has ended - it makes no sense if SetEvent is not called.

+2
source

As you know, Microsoft gives the following example of why we might need SignalObjectAndWait if we already need separate SetEvent and WaitForSingleObject (specify Microsoft example):

A thread can use the SignalObjectAndWait function to ensure that a worker thread is in a wait state before signaling an object. For example, a thread and a worker thread can use descriptors for event objects to synchronize their work. A thread executes code, for example:

 dwRet = WaitForSingleObject(hEventWorkerDone, INFINITE); if( WAIT_OBJECT_0 == dwRet) SetEvent(hEventMoreWorkToDo); 

The worker thread executes the following code:

 dwRet = SignalObjectAndWait(hEventWorkerDone, hEventMoreWorkToDo, INFINITE, FALSE); 

This flow of algorithms is erroneous and should never be used. We don’t need such an intricate mechanism when threads notify each other until we reach the “race state”. Microsoft itself in this example creates a race condition. A worker thread should simply wait for an event and accept tasks from the list, while a thread that generates tasks should simply add tasks to this list and signal the event. Thus, we just need one event, not two, as in the Microsoft example above. The list must be protected by a critical section. The thread that generates tasks should not wait for the workflow to complete. If there are tasks requiring to notify someone of their completion, tasks must send notifications on their own. In other words, the task is that it will notify the thread of completion - this is not the thread that specifically waits for the thread of tasks until it completes processing all the tasks.

Such an erroneous design, as in the example of Microsoft, creates an imperative for monsters such as the atomic SignalObjectAndWait and the atomic function PulseEvent, which ultimately lead to death.

Here is an algorithm on how you can achieve the goal set in your question. The goal is achieved using simple and simple events and the simple function SetEvent and WaitForSingleObject - no other functions are required.

  • Create one general auto-reset event for all task flows to signal the presence of a task (tasks); and also create auto-reset streaming events, one event for each job stream.
  • After several tasks are completed, everyone waits for this general auto-reset "task available" event using WaitForMultipleObjects - it waits for two events - the general event and its own thread event.
  • The scheduler thread places new (pending) tasks in the list.
  • Access to the job list must be protected with EnterCriticalSection / LeaveCriticalSection, so no one ever accesses this list in any other way.
  • Each of the tasks, after completing one task, before proceeding to the auto-reset event "Task Availability" and its own event, checks the list of waiting tasks. If the list is not empty, output one task from the list (remove it from the list) and execute it.
  • There should be another list protected by the critical sector - a list of threads of waiting jobs.
  • Before the runs of each job start to wait, that is, before it calls WaitForMultipleObjects, it will add itself to the waiting list. When you exit a wait, it is removed from this wait list.
  • When the scheduler thread places new (pending) tasks in the task list, it first enters the critical section of the task list, and then the tread list - therefore, two critical sections are entered at the same time. However, task flows can never be included in both critical sections at the same time.
  • If only one task is running, the scheduler sets the general auto-reset event to the alarm state (SetEvent call) - it does not matter which of the sleeping tasks the task will run.
  • If two or more tasks are expected, this will not signal a common event, but will count how many threads are waiting. If there are any number of threads waiting when there are tasks, signal your own event of this number of threads, as there are events, and leave the remaining thread to continue to sleep.
  • If there are more jobs than waiting threads, signal your own event for each of the waiting threads.
  • After the scheduler thread has signaled all events, it leaves critical sections - first a list of threads, and then a list of tasks.
  • After the scheduler thread has signaled all the events necessary for a particular case, it goes into sleep mode, i.e. raises WaitForSingleObject with its own sleep event (this is also an auto-reset event, which should be signaled whenever a new task appears).
  • Since the task flows will not start to sleep until the entire list of tasks is exhausted, you will no longer need the scheduler thread. The scheduler thread will be needed only later when new tasks appear, and not when the task is completed by the task flow.

Important: this scheme is based solely on auto-reset events. You will never need to call ResetEvent. All necessary functions: SetEvent and WaitForMultipleObjects (or WaitForSingleObject). Operation with an atomic event is not required.

Please note: when I wrote that the thread is sleeping, it does not call the "Sleeping" API call - it will never be needed, it is simply in the "standby" state as a result of calling WaitForMultipleObjects (or WaitForSingleObject).

As you know, the auto-reset event and the SetEvent and WaitForMultipleObjects functions are very reliable. They exist with NT 3.1. You can always architect software logic that relies solely on these simple functions, so you never need complex and unreliable functions that involve atomic operations such as PulseEvent or SignalObjectAndWait. By the way, SignalObjectAndWait appeared only in Windows NT 4.0, while SetEvent and WaitForMultipleObjects existed from the initial version of Win32 - NT 3.1.

0
source

Source: https://habr.com/ru/post/951928/


All Articles