Thread synchronization mode - semaphore

Posted by divinequran on Mon, 31 Jan 2022 11:14:32 +0100

1 semaphore

Semaphore, sometimes called semaphore, is a facility used in multi-threaded environment. It can be used to ensure that two or more key code segments are not called concurrently.

Before entering a key code segment, the thread must obtain a semaphore; Once the critical code segment is complete, the thread must release the semaphore. Other threads that want to enter this critical code segment must wait until the first thread releases the semaphore. To complete this process, you need to create a semaphore VI, and then place Acquire Semaphore VI and Release Semaphore VI at the beginning and end of each key code segment respectively. Confirm that these semaphores VI refer to the semaphores originally created.

The semaphore kernel object is used to count resources. Like all other kernel objects, they also contain a usage count, but they also contain two other 32-bit values: a maximum resource and a current resource count.

  • The maximum resource count indicates the maximum resource that the semaphore can control;
  • The current resource count represents the currently available resources.

The rules of semaphore are as follows:

  • If the current resource count is greater than 0, the semaphore is triggered
  • If the current resource count is equal to 0, the semaphore is in the non triggered state
  • The system will never make the current resource count negative
  • The current resource count will never be greater than the maximum resource count

1.1 description

Take the operation of a parking lot as an example:

  • Assuming that the parking lot has only three parking spaces (shared resources), all three parking spaces are empty at the beginning.
  • At this time, if five vehicles (threads) come at the same time, the gatekeeper (semaphore) allows three of them (threads) to enter directly;
  • Then put down the car stop, and the remaining cars must wait at the entrance, and later cars have to wait at the entrance;
  • At this time, a car (thread) left the parking lot. After the gatekeeper (semaphore) learned about it, he opened the car stop and put it in an outside car;
  • If you leave two more cars, you can put in two more cars and go back and forth.

In the abstract, the characteristics of semaphore are as follows: semaphore is a non negative integer (number of vehicles), and all threads / processes (vehicles) passing through it will subtract this integer by one (through which one resource is used); When the integer value is zero, all threads (vehicles) trying to pass through it will be in a waiting state.

In terms of semaphores, we define two operations: Wait (Wait function) and release (release function). When a thread calls the Wait operation, it either gets the resource and reduces the semaphore by one, or waits until the semaphore is greater than or equal to 1. Release corresponds to the vehicle leaving the parking lot. This operation is called "release" because it releases the resources (parking spaces) guarded by the semaphore.

1.2 classification

  • Integer semaphore: semaphores are integers
  • Record semaphore: for each semaphore s, in addition to an integer value s.value (count), there is also a process waiting queue s.L
  • Binary semaphore: only 0 or 1 values are allowed for semaphores

Each semaphore must record at least two information: the value of the semaphore and the process queue waiting for the semaphore.

1.3 PV operation

The concepts of PV operation and semaphore were put forward by Dutch scientist E.W.Dijkstra. Semaphore S is an integer. When S is greater than or equal to zero, it represents the number of resource entities available for concurrent processes, but when S is less than zero, the absolute value of S represents the number of processes waiting to use shared resources.

◉ P operation application resources:
1) S minus 1;
2) if S minus 1 is still greater than or equal to zero, the process continues;
3) if S minus 1 is less than zero, the process is blocked and enters the queue corresponding to the signal, and then enters process scheduling.
  
◉ V operation releases resources:
1) S plus 1;
2) if the addition result is greater than zero, the process continues to execute;
3) if the addition result is less than or equal to zero, wake up a waiting process from the waiting queue of the signal, and then return to the original process to continue execution or transfer to process scheduling.

1.4 description

Signals allow multiple threads to use shared resources at the same time, which is the same as PV operation in the operating system. It indicates the maximum number of threads accessing shared resources at the same time. It allows multiple threads to access the same resource at the same time, but it needs to limit the maximum number of threads accessing this resource at the same time.

When creating semaphores with CreateSemaphore(), you should indicate both the maximum allowed resource count and the current available resource count. Generally, the current available resource count is set to the maximum resource count. For each additional thread's access to shared resources, the current available resource count will be 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 decreases to 0, it indicates that the number of threads currently occupying resources has reached the maximum allowed number. Other threads cannot be allowed to enter, and the semaphore signal will not be sent at this time. After the thread finishes processing the shared resources, it should increase the current available resources count by 1 through the ReleaseSemaphore function while leaving. The current available resource count can never be greater than the maximum resource count at any time.

2 semaphores contain several operation primitives

There are four operations on semaphores (#include < semaphore. H >):

  • CreateSemaphore() creates a semaphore
  • OpenSemaphore() opens a semaphore
  • ReleaseSemaphore() releases semaphore
  • WaitForSingleObject() wait semaphore

Semaphore semaphore commonly has three functions, which are very convenient to use. The following are the prototypes and instructions of these functions.

1)CreateSemaphore

  1. Function function: create semaphore.

  2. Function prototype:

    HANDLE   CreateSemaphore( 
    	LPSECURITY ATTRIBUTES  lpSemaphoreAttributes, //Security attributes 
    	LONG  lInitialCount, //Sets the initial count of semaphores
    	LONG  lMaximumCount, //Sets the maximum count of semaphores 
    	LPCTSTR  lpName       //Specifies the name of the semaphore object
    );
    
    
  3. Parameter Description:

  4. Return value

    If the semaphore is created successfully, the handle of the semaphore will be returned.
    If the given semaphore name is a semaphore that already exists in the system, the handle of the existing semaphore will be returned.
    If it fails, the system returns NULL. You can call the function GetLastError() to query the reason for the failure.

2) OpenSemaphore

  1. Function function: open semaphore and create a new handle for an existing named semaphore object. Like other core objects, semaphores can be accessed across processes by name

  2. Function prototype:

    HANDLE  OpenSemaphore(
    		DWORD  dwDesiredAccess,
    		BOOL  bInheritHandle,
    		LPCTSTR  lpName
    );
    
    
  3. Function Description:

3) ReleaseSemaphore

  1. Function function: increment the current resource count of semaphore.

  2. Function prototype:

    BOOL  ReleaseSemaphore(
    	HANDLE  hSemaphore,
    	LONG  lReleaseCount, 
    	LPLONG  lpPreviousCount 
    );
    
    
  3. Function Description:

5)WaitForSingleObject

  1. Function function: the WaitForSingleObject function is used to detect the state of the signal. When a function is called in a thread, the thread is temporarily suspended. If the object waiting for the thread becomes a signal state in the suspended dwMilliseconds milliseconds, the function will return immediately. If the time has reached dwmilliseconds, but the object pointed to by the hHandle has not changed into a signaled state, the function returns as usual.

  2. Function prototype:

    Wait for an event :
    DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
    Wait for multiple events :
    DWORD WaitForMultipleObjects(
          DWORD nCount, // Number of waiting handles 
          CONST HANDLE *lpHandles, //Point to handle array 
          BOOL bWaitAll, //Complete wait flag 
          DWORD dwMilliseconds //waiting time 
        )
    
    
  3. Function Description:

  4. Return value

    The execution is successful, and the return value indicates the event causing the return of the function. It may be the following values:

5)CloseHandle

Clearing and destruction of semaphores:

  • Since semaphores are kernel objects, they can be cleaned and destroyed by using CloseHandle().

3 application and examples

Dual purpose of semaphores: mutual exclusion and synchronization
Advantages of semaphore: no busy, etc

#include <iostream>
#include "windows.h"  
using namespace std;
DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);
int number = 1;
HANDLE hSemaphore;  //Define semaphore handle
void main()
{
    HANDLE hThread1;
    HANDLE hThread2;
    hSemaphore = CreateSemaphore(
        NULL,//Specify the security attribute. Generally, NULL is passed in
        1,   //Specifies the initial value of the semaphore object. The value must be greater than or equal to 0
        100, //Indicates the maximum value of the semaphore, which must be greater than 0.
        NULL//Give the name of the semaphore.
    );      //Currently, there is one resource, and 100 resources are allowed to access at the same time   
    hThread1 = CreateThread(
        NULL, //NULL means that the returned handle cannot be inherited by the child process
        0,   //Set the initial stack size in bytes. If it is 0, the same stack space size as the thread calling the function will be used by default.
        FunProc1, //Pointer to thread function
        NULL, //The parameter passed to the thread function is a pointer to the structure. It is NULL when there is no need to pass the parameter.
        0, //Flag that controls thread creation. 0: indicates activation immediately after creation
        NULL //Save the id of the new thread, which is the pointer to the thread id. if it is empty, the thread id will not be returned
    ); //If the function succeeds, the thread handle is returned; otherwise, NULL is returned
    hThread2 = CreateThread(NULL, 0, FunProc2, NULL, 0, NULL);//Activate immediately after creation
    if (hThread1 != NULL)CloseHandle(hThread1);
    if (hThread2 != NULL)CloseHandle(hThread2);
    Sleep(20000);             // Let the main thread sleep for 1 second     
    if (hSemaphore != NULL)CloseHandle(hSemaphore);
 

}
DWORD WINAPI FunProc1(LPVOID lpParameter)
{
    long count;
    while (number<25)
    {
        WaitForSingleObject(hSemaphore, INFINITE);//The semaphore is requested when hSemaphore is idle    
        Sleep(1);
        cout << "FunProc 1:" << number << endl;
        ++number;
        ReleaseSemaphore(
            hSemaphore,//Semaphore handle
            1,      //Add "1" to the current resource count
            &count //Returns the original value of the current resource count
        );
    }
    return 0;
}
DWORD WINAPI FunProc2(LPVOID lpParameter)
{
    long count;
    while (number < 25)
    {
        WaitForSingleObject(hSemaphore, INFINITE);//The semaphore is requested when hSemaphore is idle     
        Sleep(1);
        cout << "FunProc 2:" << number << endl;
        ++number;
        ReleaseSemaphore(
            hSemaphore,//Semaphore handle
            1,      //Add "1" to the current resource count
            &count //Returns the original value of the current resource count
        );
    }
    
    return 0;
}

Topics: C++