Semaphores for synchronization and mutual exclusion

Directory

1. The semaphore is used for mutual exclusion of threads

verify

2. The semaphore is used for thread synchronization

verify

3. The unnamed semaphore is used for inter-process mutual exclusion

code one

code two

verify

4. The famous semaphore is used for inter-process synchronization and mutual exclusion

verify


Semaphore is widely used for synchronization and mutual exclusion between processes or threads. Semaphore is essentially a non-negative integer counter, which is called
Used to control access to public resources. When the semaphore value is greater than 0, it can be accessed, otherwise it will block. A semaphore is a mechanism for controlling synchronization and mutual exclusion of processes or threads. It usually consists of a counter and a set of waiting processes or threads. When a process or thread needs to access a shared resource, it tries to acquire a semaphore. If the semaphore’s counter is greater than 0, the process or thread can acquire the semaphore and continue execution. Otherwise, the process or thread will be blocked until a semaphore is available.

The PV primitive is the operation on the semaphore. A P operation makes the semaphore decrement by 1, and a V operation makes the semaphore add 1.

The semaphore data type is: sem_t

1. The semaphore is used for mutual exclusion of threads

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

// Define a semaphore (for mutual exclusion)
sem_t sem;

void my_printf(char *str)
{
    int i = 0;
    while (str[i] != '\0')
    {
        printf("%c", str[i + + ]);
        fflush(stdout);
        sleep(1);
    }
}
void *fun1(void *arg)
{
    // P operation
    sem_wait( & amp; sem);
    my_printf((char *)arg);
    // V operation
    sem_post( &sem);
}
void *fun2(void *arg)
{
    // P operation
    sem_wait( & amp; sem);
    my_printf((char *)arg);
    // V operation
    sem_post( &sem);
}
void *fun3(void *arg)
{
    // P operation
    sem_wait( & amp; sem);
    my_printf((char *)arg);
    // V operation
    sem_post( &sem);
}
int main(int argc, char *argv[])
{
    // The semaphore is initialized to 1. The second parameter 0 means it is used for threads, and the third semaphore initial value
    sem_init( & sem, 0, 1);
    pthread_t tid1, tid2, tid3;

    pthread_create( & amp; tid1, NULL, fun1, "this is tid1\
");
    pthread_create( &tid2, NULL, fun2, "this is tid2\
");
    pthread_create( &tid3, NULL, fun3, "this is tid3\
");

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    //destroy the semaphore
    sem_destroy( &sem);
    return 0;
}

The concurrent output of three threads is realized, but the semaphore sem is used to ensure that only one thread accesses the standard output stream during output, that is, mutual exclusion is realized.

A semaphore sem is defined for mutual exclusion access. Three threads tid1, tid2 and tid3 will run simultaneously, and their goal is to call the my_printf function to print different Strings, but these threads need to access shared resources with mutual exclusion, otherwise the output strings will be confused.

In the fun1, fun2 and fun3 functions, first call the sem_wait function to apply for semaphore resources, which means entering the critical area, if the value of the semaphore is 1, it will be decremented by 1, indicating that the application is successful, otherwise it will block and wait. Then call the my_printf function to output the corresponding string, and finally call the sem_post function to release the semaphore resource, add 1 to it, which means exit the critical section, and other threads can apply for this resource up.

In the main function, the semaphore is initialized, three threads are created, and the fun1, fun2 and fun3 functions are called respectively, and finally the three threads are waiting for execution End, destroy the semaphore.

Verify

The execution order of threads is indeterminate and determined by the operating system. Although the tid1 thread is created first in the code, the operating system may execute the tid3 thread first, so the final execution result may be that tid3 is executed first, then tid2, and finally tid1. Therefore, you cannot rely on the order in which threads are created in your code to determine the order in which threads will execute.

2. The semaphore is used for thread synchronization

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

// Define three semaphores (for synchronization)
sem_t sem1, sem2, sem3;
void my_printf(char *str)
{
    int i = 0;
    while (str[i] != '\0')
    {
        printf("%c", str[i + + ]);
        fflush(stdout);
        sleep(1);
    }
}
void *syn_fun1(void *arg)
{ //p
    sem_wait( &sem1);
    my_printf((char *)arg);
    // v
    sem_post( &sem2);
}
void *syn_fun2(void *arg)
{ //p
    sem_wait( &sem2);
    my_printf((char *)arg);
    // v
    sem_post( &sem3);
}
void *syn_fun3(void *arg)
{ //p
    sem_wait( & sem3);
    my_printf((char *)arg);
    // v
    sem_post( &sem1);
}
int main(int argc, char *argv[])
{
    // The semaphore is initialized to 1 and the second parameter 0 means it is used for the thread
    sem_init( & sem1, 0, 1);
    sem_init( & sem2, 0, 0);
    sem_init( & sem3, 0, 0);

    pthread_t tid1, tid2, tid3;

    pthread_create( &tid1, NULL, syn_fun1, "this is tid1\
");
    pthread_create( &tid2, NULL, syn_fun2, "this is tid2\
");
    pthread_create( &tid3, NULL, syn_fun3, "this is tid3\
");

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    // destroy the semaphore
    sem_destroy( &sem1);
    sem_destroy( & sem2);
    sem_destroy( &sem3);

    return 0;
}

The synchronous execution of three threads is realized, that is, three strings are output in sequence according to the specified order. Among them, three semaphores are used to achieve synchronization, namely sem1, sem2, and sem3. The initial values of these three semaphores are 1, 0, and 0 respectively, indicating that sem1 can be accessed, while sem2 and sem3 need to wait for signals from other threads to be accessed. The three threads correspond to the syn_fun1, syn_fun2, and syn_fun3 functions respectively, and each function uses sem_wait and sem_post to operate the semaphore. Among them, sem_wait is used for P operation, which is to try to acquire the semaphore. If the value of the semaphore is 0, the thread will block and wait for the signal of other threads; and sem_post is used for the V operation, that is, to release the semaphore and increase the value of the semaphore. 1, indicating that other threads can access this semaphore. Finally, create three threads in the main function, and use pthread_join to wait for the threads to end, and finally destroy the semaphore.

Authentication

Although three semaphores are defined, there is only one semaphore with an initial value of 1. Therefore, only the sem1 corresponding to the semaphore of 1 can run, and the others need to wait for the signal, which ensures that only tid1 runs first, not run randomly like mutex above

3. Unnamed semaphore is used for inter-process mutual exclusion

An unnamed semaphore is a special type of semaphore that can only be used by threads within the same process. They don’t need to be named, hence the name “unnamed semaphores”. Unnamed semaphores are often used to control synchronization and mutual exclusion between threads. In the code example, the sem_init function is used to initialize an unnamed semaphore, while the sem_wait and sem_post functions are used to wait for and release the semaphore, respectively.

Code 1

#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/mman.h>


void my_printf(char *str)
{
    int i = 0;
    while (str[i] != '\0')
        printf("%c", str[i + + ]);
    fflush(stdout);
    sleep(1);
}
int main(int argc, char *argv[])
{
    // define an unnamed semaphore
    // MAP_ANONYMOUS anonymous mapping -1 does not require a file descriptor
    sem_t *sem = mmap(NULL,
                      sizeof(sem_t),
                      PROT_READ |
                      PROT_WRITE,
                      MAP_SHARED|
                      MAP_ANONYMOUS,
                      -1, 0);
    // Initialization of the unnamed semaphore The first 1 indicates the process The second 1 indicates the initialization value 1
    sem_init(sem, 1, 1);
    pid_t pid = fork();
    if (pid == 0) // child process
    {
        //p
        sem_wait(sem);
        my_printf("child process\
");
        // v
        sem_post(sem);
    }
    else if (pid > 0) // parent process
    {
        //p
        sem_wait(sem);
        my_printf("father process\
");
        // v
        sem_post(sem);
    }
    sem_destroy(sem);
    return 0;
}

By calling the mmap function, the semaphore is mapped to the virtual memory space of the process. Then, use sem_init to initialize the value of the semaphore to 1, indicating that no other process is currently accessing it. Next, create a child process by calling the fork function. In the parent process, call the sem_wait function to try to gain access to the semaphore. Since this is the first process, it is able to gain access to the semaphore. It prints a message and then uses the sem_post function to release access to the semaphore. In the child process, it will also try to gain access to the semaphore, but since the parent process has already gained access to the semaphore, the child process must wait for the parent process to release access to the semaphore. It then prints a message and uses the sem_post function again to release access to the semaphore. Finally, call the sem_destroy function to destroy the semaphore and release the resource.

mmap is a system call used to map a file or device into memory. In the above code, its function is to allocate a memory area for shared memory, and return a pointer to this area. The calling parameters of the mmap function are explained as follows:

  • NULL: Indicates the starting address of the allocated memory area, which is automatically allocated by the system
  • sizeof(sem_t): Indicates the size of the memory area to be allocated
  • PROT_READ | PROT_WRITE: Indicates the access rights of the memory area, here is readable and writable
  • MAP_SHARED: Indicates that this memory area is shared by multiple processes, using the MAP_ANONYMOUS flag to create an anonymous mapping, an unnamed shared memory area that does not need to be associated with a file
  • fd: Indicates the file descriptor, here is the file descriptor of the shared memory, ‐1 does not need a file descriptor
  • 0: Indicates the offset, there is no offset here, and the mapping starts from the beginning of the file
  • Success returns the first address of the mapping area

Code 2

#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/mman.h>


void my_printf(char *str)
{
    int i=0;
    while(str[i] != '\0')
    {
        printf("%c", str[i + + ]);
        fflush(stdout);
        sleep(1);
    }
}
int main(int argc, char *argv[])
{
    //Define an unnamed semaphore
 //MAP_ANONYMOUS anonymous mapping -1 does not require a file descriptor
    sem_t *sem1 = mmap(NULL, sizeof(sem_t),
                        PROT_READ|PROT_WRITE,
                        MAP_SHARED | MAP_ANONYMOUS,
                        -1,0);
    sem_t *sem2 = mmap(NULL, sizeof(sem_t),
                        PROT_READ|PROT_WRITE,
                        MAP_SHARED | MAP_ANONYMOUS,
                        -1,0);
    //Initialization of unnamed semaphore The first 1 indicates the process The second 1 indicates the initialization value 1
    sem_init(sem1,1,1);
    sem_init(sem2,1,0);

    pid_t pid = fork();
    if(pid ==0)//subprocess
    {
        //p
        sem_wait(sem1);
        my_printf("this is child process\
");
        //v
        sem_post(sem2);
    }
    else if(pid >0)
    {
        //p
        sem_wait(sem2);
        my_printf("this is father process\
");
        //v
        sem_post(sem1);
    }
    //destroy the semaphore
    sem_destroy(sem1);
    sem_destroy(sem2);
    return 0;

}

This code defines two unnamed semaphores sem1 and sem2, and uses mmap to map them to the virtual memory space of the process respectively. Then use these two unnamed semaphores to achieve synchronization between processes. Different from the previous piece of code, this code uses two different pointer variables sem1 and sem2 to point to two unnamed semaphores mapped to memory respectively. At the same time, when creating a child process, the child process waits for the parent process to open it through sem2, and the parent process waits for the child process to open it through sem1. This achieves synchronization between processes. In general, these two pieces of code use unnamed semaphores to achieve inter-process synchronization, except that one uses an unnamed semaphore and the other uses two unnamed semaphores.

Authentication

The results of the two codes print the same information

4. The famous semaphore is used for inter-process synchronization and mutual exclusion

#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
void my_printf(char *str)
{
   int i = 0;
   while (str[i] != '\0')
   {
      printf("%c", str[i + + ]);
      fflush(stdout);
      sleep(1);
   }
}

int main(int argc, char **argv)
{
   // Create 2 well-known semaphores sem_open, the last parameter is the initial value
   sem_t *sem1 = sem_open("sem1", O_RDWR | O_CREAT, 0666, 1);
   sem_t *sem2 = sem_open("sem2", O_RDWR | O_CREAT, 0666,0);
   //p
   sem_wait(sem1);
   // Task
   my_printf("this is sem1\
");
   // v
   sem_post(sem2);
   // Task
   my_printf("this is sem2\
");
   //p
   sem_post(sem1);
   // close the semaphore
   sem_close(sem1);
   sem_close(sem2);
   // destroy the semaphore
   sem_destroy(sem1);
   sem_destroy(sem2);
   return 0;
}

The process obtains the sem1 semaphore through sem_wait(sem1). If the value of sem1 is 1 at this time, it will be decremented by 1, indicating that resources are occupied , otherwise block and wait. The process outputs "this is sem1\
"
, at this time other processes cannot obtain the sem1 semaphore, thus achieving mutual exclusion. The process releases the sem2 semaphore through sem_post(sem2), adds 1 to its value, and wakes up other processes blocked on sem2, thus realizing Synchronize. At this time, other processes can obtain the sem2 semaphore and proceed to the next step. The process outputs "this is sem2\
"
, and other processes cannot obtain the sem2 semaphore, thus achieving mutual exclusion.

After the second task is executed, call sem_post(sem1) to increase the value of sem1 by one, and then you can re-enter the first task. This method can guarantee the execution order of the first task and the second task, and the first task will not be re-executed until the second task is completed. At the same time, since the initial value of sem1 is 1, it is guaranteed that only one process can access the code of the first task, realizing mutual exclusion

verify