Thread synchronization mutex named semaphore, unnamed semaphore

1. Thread mutual exclusion mode

1. What is a synchronous mutual exclusion? Why deal with synchronized mutexes?

Synchronization mutual exclusion is to make threads process tasks in a sequential order, in order to prevent thread resources from being preempted.

2. What are the ways to deal with synchronous mutual exclusion?

Semaphore -> Process (shared memory + semaphore used together) — one of the process IPC

Named semaphore -> process (shared memory + named semaphore used together)

unnamed semaphore -> thread

mutex -> thread

read-write lock -> thread

Condition variable -> thread (mutex + condition variable used together)

2. Basic concepts

The logic of the POSIX semaphore is exactly the same as that of the semaphore element in the IPC semaphore group, but the operation of the POSIX semaphore is easier and the interface is easier to use. It is widely used in multi-process and multi-thread.

POSIX semaphores are divided into two types:

  • POSIX anonymous semaphores
    • Usually used between threads
    • Only exists in memory, not visible in the file system
  • POSIX named semaphores
    • Usually used between processes
    • It exists in the file system /dev/shm and can be operated by different processes

3. Well-known semaphore

POSIX named semaphore is mainly used to synchronize mutual exclusion between multiple processes, its P/V operation is the same as the anonymous version, its biggest feature is that it exists in the file system /dev /shm, which can be opened by any privileged process in the system

The function interface description of the well-known semaphore is as follows:

sem_t *sem;
1) Create and open a well-known semaphore -> sem_open()
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
Function role:
Initialize and open a named semaphore
parameter:
name: The name of the famous semaphore, which must start with "/", such as "/sem_test or /my_sem" -> exist in the /dev/shm directory
    oflag: O_CREAT -> create if does not exist
           O_CREAT|O_EXCL -> Create if it does not exist -> Report an error if it exists
    mode: octal permission, for example: 0666
    value: the starting value of the well-known semaphore ---> 0
Notice:
If there is an option O_CREAT in oflag, the mode and value must be filled.
    If the famous semaphore already exists, but you write O_CREAT again, then the mode and value you fill in later will be ignored.
return value:
success: the address of the well-known semaphore
    Failed: SEM_FAILED -> NULL / (sem_t *)-1
sem_t* sem = sem_open(SEM_NAME,O_CREAT,0777,0);
    if(sem == SEM_FAILED)
    {
        printf("sem_open fail\\
");
     return -1;
    }
 
2) P operation of well-known semaphore
P operation: resource number - 1 operation -> sem_wait() -> man 3 sem_wait
#include <semaphore.h>
int sem_wait(sem_t *sem); -> If it cannot be reduced by 1, this function will block
parameter:
sem: the address of the well-known semaphore
return value:
Success: 0 -> resource count can -1
    Failed: -1
for example:
If the current value is 2, then sem_wait() will return immediately and set the value to 1.
If the current value is 1, then sem_wait() will return immediately and set the value to 0.
If the current value is 0, then sem_wait() will block until the value of the named semaphore is not 0.

3) V operation of well-known semaphore
V operation: number of resources + 1 operation -> sem_post() -> man 3 sem_post
#include <semaphore.h>
int sem_post(sem_t *sem); -> must be able to + 1, absolutely will not block
parameter:
sem: the address of the well-known semaphore
return value:
Success: 0 -> number of resources can + 1
    Failed: -1
4) Close the well-known semaphore. -> sem_close() -> man 3 sem_close
#include <semaphore.h>
int sem_close(sem_t *sem);
parameter:
sem: the address of the well-known semaphore
return value:
Success: 0
    Failed: -1
5) Delete the well-known semaphore. -> sem_unlink() -> man 3 sem_unlink
#include <semaphore.h>
int sem_unlink(const char *name); (named semaphore name)
parameter:
name: the name of the well-known semaphore
return value:
Success: 0
    Failed: -1
 When compiling a famous semaphore, you need to add a thread library to compile

Well-known semaphores are tested as follows:

#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <string.h>

#define SEM_NAME "/semname1"

//Famous semaphore _wr.c
int main()
{
    //1. Get the key value
    key_t key = ftok(".",10);
    //2. Obtain the ID number of the shared memory according to the key value
    int shmid = shmget(key,1024,IPC_CREAT|0666);
    //3. Map the shared memory to a certain area of the virtual memory space of the process according to the ID number
    char*shm_p = shmat(shmid,NULL,0);
    if(shm_p == (void*)-1)
    {
        perror("shmat error");
        return -1;
    }
    
    //4. Create and open a well-known semaphore
    sem_t *nameSem = sem_open(SEM_NAME,O_CREAT,0777,0);
    if(nameSem == SEM_FAILED )
    {
        printf("sem_open error\\
");
        return -1;
    }
    
    //The shm_p mapped out at this time is the shared memory of the two processes
    while(1)
    {
        // Get data from the keyboard and store it in the shared memory shm_p
        scanf("%s",shm_p);
    
        //V operation of well-known semaphore data + 1
        sem_post(nameSem);
        
        //Exit condition, here should be noted that strncmp should be used to specify the number of bytes
        if(strncmp(shm_p,"exit",4) == 0)
            break;
    }
    //4. When it is no longer used, release the mapping relationship
    shmdt(shm_p);
    //5. When no process needs to use this shared memory anymore, delete it
    shmctl(shmid, IPC_RMID, NULL);
    //6. Close the well-known semaphore.
    sem_close(nameSem);
    //7. Delete the famous semaphore
    sem_unlink(SEM_NAME);
    
    return 0;
}

//Famous semaphore_rd.c
int main()
{
    //1. Get the key value
    key_t key = ftok(".",10);
    //2. Obtain the ID number of the shared memory according to the key value
    int shmid = shmget(key,1024,IPC_CREAT|0666);
    //3. Map the shared memory to a certain area of the virtual memory space of the process according to the ID number
    char*shm_p = shmat(shmid,NULL,0);
    if(shm_p == (void*)-1)
    {
        perror("shmat error");
        return -1;
    }
    //4. Create and open a well-known semaphore
    sem_t *nameSem = sem_open(SEM_NAME,O_CREAT,0777,0);
    if(nameSem == SEM_FAILED )
    {
        printf("sem_open error\\
");
        return -1;
    }
    
    //The shm_p mapped out at this time is the shared memory of the two processes
    while(1)
    {
        //P operation data of well-known semaphore -1
        sem_wait(nameSem);
        
        // read data from shared memory
        printf("recv:%s\\
",shm_p);
        
        //Exit condition, here should be noted that strncmp should be used to specify the number of bytes
        if(strncmp(shm_p,"exit",4) == 0)
            break;
    }

    return 0;
}

4. Comparison between famous semaphore and semaphore

difference:

1) The semaphore function is complex, and the well-known semaphore function is simple.

2) The semaphore has space and data to operate; the famous semaphore only needs to operate on the data.

3) The thread library is not required when compiling the semaphore; the thread library needs to be linked when compiling the famous semaphore.

Same point:

1) Both act on communication between processes

2) There are P/V operations; P is minus 1 operation, V is plus 1 operation

5. Unnamed semaphore

It is generally used for mutual exclusion between threads. Since it is an unnamed semaphore, it has no name and cannot be opened with sem_open

The function interface description of the unnamed semaphore is as follows:

1) Define an unnamed semaphore (data type: sem_t) ---> global variable
sem_t sem; -> The unnamed semaphore is not a file, but a variable, so the sem_open() function cannot be used
2) Initialize unnamed semaphore -> sem_init() -> man 3 sem_init
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
parameter:
sem: the address of the variable of the unnamed semaphore
    pshared:
        0 -> act between threads -> only consider this case
    Non-0 -> act between processes
    value: the starting value of the unnamed semaphore ---> 1
return value:
Success: 0
    Failed: -1
sem_init( & g_sem,0,1);

3) Destroy the unnamed semaphore. -> sem_destroy() -> man 3 sem_destroy
#include <semaphore.h>
int sem_destroy(sem_t *sem);
parameter:
sem: the address of the unnamed semaphore
return value:
Success: 0
    Failed: -1

The PV operation function is the same as the well-known semaphore

The unnamed semaphore test code is as follows:

#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <semaphore.h>

//1. Define an unnamed semaphore variable
sem_t g_sem;

int g_val = 0;

//Routine function of thread 1
void* start_routine1(void *arg)
{
printf("%s start ...\\
",__FUNCTION__);

//Before accessing shared resources, check to see if other threads are using them
//P operation of unnamed semaphore
//That is to say, can the -1 operation be performed? If so, go down
//If not, block here
sem_wait( & g_sem);

g_val = 200;
sleep(1);
printf("start_routine1 g_val:%d\\
",g_val);

//When this thread no longer uses this shared resource, the unnamed semaphore performs V operation
sem_post( & g_sem);

printf("%s end ...\\
",__FUNCTION__);
}

//Routine function of thread 2
void* start_routine2(void *arg)
{
printf("%s start ...\\
",__FUNCTION__);

sem_wait( & g_sem);

sleep(1);
g_val = 400;
printf("start_routine2 g_val:%d\\
",g_val);

//When this thread no longer uses this shared resource, the unnamed semaphore performs V operation
sem_post( & g_sem);
printf("%s end ...\\
",__FUNCTION__);
}

//Routine function of thread 3
void* start_routine3(void *arg)
{
printf("%s start ...\\
",__FUNCTION__);

sem_wait( & g_sem);

sleep(1);
g_val = 600;
printf("start_routine3 g_val:%d\\
",g_val);

//When this thread no longer uses this shared resource, the unnamed semaphore performs V operation
sem_post( & g_sem);
printf("%s end ...\\
",__FUNCTION__);
}

int main()
{
//2. Initialize the unnamed semaphore ---sem_init
sem_init( & g_sem,0,1);


//Create a child thread
pthread_t thread1;
pthread_create( & thread1, NULL, start_routine1, NULL);

pthread_t thread2;
pthread_create( & thread2, NULL, start_routine2, NULL);

pthread_t thread3;
pthread_create( & thread3, NULL, start_routine3, NULL);


sem_wait( & g_sem);
g_val = 1000;
printf("main g_val:%d\\
",g_val);

//When this thread no longer uses this shared resource, the unnamed semaphore performs V operation
sem_post( & g_sem);


//Block and wait for the child thread to exit, recycle resources
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join(thread3, NULL);

//destroy unnamed semaphore
sem_destroy( & g_sem);
}

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge