Semaphore on C ++ Events

Today I will briefly talk about how I implemented a semaphore based on the synchronization object “Event”.



First go through the definitions.



1. What is synchronization and why is it needed?



Obviously, we can perform a set of actions in several ways. The simplest - in series and in parallel. Parallel implementation of certain actions can be achieved by running various threads. The idea is simple: we assign to each thread some elementary (or not so) action and start them in a certain order. Generally speaking, we can launch them all at the same time - of course, we will get a gain in time. This is understandable: it is one thing to output 10,000 words one after another, and another thing to simultaneously output, for example, 100 words. 100-fold time gain (plus or minus, excluding delays, etc.). But the original task may require a strict sequence of actions.



For example:





The hothouse example was specially taken (it is clear that no parallelism is needed here, everything can simply be done sequentially), but as a training task it will completely work, and most importantly, the need for consistent execution is clearly visible on its example. Or here is another example, slightly different:





Here, the first point can be performed simultaneously by three different threads, but the last, conclusion, must be done sequentially, and only after working out the first point.



In general, parallelism tasks can be very different and some tool is needed to synchronize threads.



2. Tools for thread synchronization



Windows.h implements quite a lot of regular synchronization tools (the so-called "synchronization objects"). Among the main ones are: critical region, event, mutex, semaphore. Yes, for semaphore there is already an implementation in windows.h. “So why program it?” You ask. Well, firstly, to get a better feel for how it works. And, secondly, the extra practice of C ++ has not stopped anyone yet :)



Given that we will use Events, we will explain what it is and how to use it.



Events are used to notify pending threads. That is, in fact, this is some signal for the flow - it can be triggered or not yet. From the very meaning of this object, it follows that it has some signal state and the ability to adjust it (reset / “turn on”).



So, after connecting windows.h we can create an event with:



HANDLE CreateEvent ( LPSECURITY_ATTRIBUTES lpEventAttributes, //   BOOL bManualReset, //  : TRUE -  BOOL bInitialState, //  : TRUE -  LPCTSTR lpName //   );
      
      





If the function succeeds, the event handle will be returned. If the object could not be created, NULL will be returned.



To change the status of the event to signal, we use the function:



 BOOL SetEvent ( HANDLE hEvent //   );
      
      





If successful, returns a nonzero value.



Now about the semaphore. The semaphore is designed to control the number of simultaneously running threads. Suppose we have 1000 threads, but only 2 can work at a time. This is the type of adjustment that happens with the help of a semaphore. And what functions are implemented to work with this synchronization object?



To create a semaphore, similar to events:



 HANDLE CreateSemaphore ( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //   LONG lInitialCount, //     LONG lMaximumCount, //    LPCTSTR lpName //   );
      
      





If successful, we get a pointer to the semaphore, if it fails, we get NULL.



The semaphore counter is constantly changing (the thread is running and a vacant place appears), so the state of the semaphore needs to be changed periodically. This is done using this function:



 BOOL ReleaseSemaphore ( HANDLE hSemaphore, //    LONG lReleaseCount, //     LPLONG lpPreviousCount //   );
      
      





If successful, the return value is not zero.



Also worth paying attention to the function:



 DWORD WaitForSingleObject( HANDLE hHandle, //   ,     DWORD dwMilliseconds //     );
      
      





Of the returned values, we are particularly interested in 2: WAIT_OBJECT_0 - means that the state of our object is signal; WAIT_TIMEOUT - we did not wait for the signal state from the object in the allotted time.



3. The task itself



In total, our task is to write our analogues on regular functions. We will not complicate the task much, we will make “implementation in the first approximation”. The main thing is to preserve the quantitative characteristics of the standard semaphore. Code with comments can be found on GitHub .



Due to the simplicity of the task itself, we will not particularly complicate the article, but it may come in handy for someone :)



All Articles