Multithreaded synchronization

Keywords: Linux Hibernate

1.linux uses a multithreaded synchronization method

1) Mutex: When thread A locks mutex variables, thread B is suspended until thread A unlocks them.
Note: Conditional variables can be used to increase efficiency when threads are constantly polling to check a condition to determine whether they can operate on data that needs to be synchronized.
The demo is as follows:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
​
pthread_mutex_t mutex;
​
void *print_msg(void *arg)
{
    int i = 0;
    pthread_mutex_lock(&mutex); //Mutex Lock Locking
    for (i = 0; i < 20; i++)
    {
        printf(" i = %d\n", i);
        usleep(200);
    }
    pthread_mutex_unlock(&mutex);
​
}
​
int main()
{
    pthread_t id1, id2;
    pthread_mutex_init(&mutex, NULL);
    pthread_create(&id1, NULL, print_msg, NULL);
    pthread_create(&id2, NULL, print_msg, NULL);
    pthread_join(id1, NULL); //Causes the main thread to wait for the thread to finish before it finishes, otherwise the main thread finishes quickly and the thread has no opportunity to execute
    pthread_join(id2, NULL);
    pthread_mutex_destroy(&mutex);
​
    return 0;
}

2) Semaphore: Actually, it is an integer. As long as the value of the semaphore is greater than 0, other threads can sem_wait successfully, and the value of the semaphore will be reduced by 1 after success.If the value is not greater than 0, sem_wait causes the thread to block until the value is added to 1 after the sem_post is released, but it is still reduced by 1 before the sem_wait returns.
The demo is as follows:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
​
void *thread_func(void* msg);
sem_t sem;
sem_t sem_add;
​
#define MSG_SIZE 512
​
int main()
{
    int res = -1;
    pthread_t thread;
    void *thread_result = NULL;
    char msg[MSG_SIZE];
​
    res = sem_init(&sem, 0, 0);
    if (res == -1)
    {
        printf("sem init failed\n");
        exit(-1);
    }
​
    res = sem_init(&sem_add, 0, 1);
    if (res == -1)
    {
        printf("sem_add init failed\n");
        exit(-1);
    }
​
    res = pthread_create(&thread, NULL, thread_func, msg);
    if (res != 0)
    {
        printf("pthread_create failed\n");
        exit(-1);
    }
    printf("input some text. Enter 'end' to finish...\n");
    
    sem_wait(&sem_add);
    while(strcmp("end\n", msg) != 0)
    {
        if (strncmp(msg, "TEST", 4) == 0)
        {
            strcpy(msg, "copy_data\n");
            sem_post(&sem);
            sem_wait(&sem_add);
        }
        fgets(msg, MSG_SIZE, stdin);
        sem_post(&sem); //sem semaphore plus 1 to start execution of child threads
        sem_wait(&sem_add); //sem_add semaphore minus 1, waiting for sub-thread processing to complete
​
    }
​
    printf("waiting for thread to finish...\n");
    res = pthread_join(thread, &thread_result);
    if (res != 0)
    {
        printf("pthread_join faild\n");
        exit(-1);
    }
​
    printf("thread joined\n");
​
    sem_destroy(&sem);
    sem_destroy(&sem_add);
    exit(0);
​
    return 0;
}
​
void* thread_func(void* msg)
{
    char *ptr = (char*)msg;
    sem_wait(&sem);
    while(strcmp("end\n", ptr) != 0)
    {
        int i = 0;
        for (; ptr[i] != '\0'; ++i )
        {
            if (ptr[i] >= 'a' && ptr[i] <= 'z')
            {
                ptr[i] -= 'a' - 'A';
            }
        }
        printf("you input %d characters\n", i - 1);
        printf("to uppercase: %s\n", ptr);
​
        sem_post(&sem_add);
        sem_wait(&sem);
    }
    sem_post(&sem_add);
    pthread_exit(NULL);
}

3) Conditional variables: Used frequently with mutexes, conditional variables are used to block a thread. When conditions are not met, the thread unlocks the mutex and waits for the condition to change. Once another thread changes the condition variable, it notifies the corresponding conditional variable to wake up one or more threads that are being blocked by this variable, and the threads become heavyRe-lock the mutex and re-test if the condition is met.
pthread_cont_init()
pthread_cont_destroy()
pthread_cont_wait()//thread unlocks the lock pointed to by mutex and is blocked by a conditional variable
pthread_cont_timedwait()//Too many time parameters, after which the blocking is released even if the conditional variable is not satisfied
pthread_cont_signal()/pthread_cont_broadcast//Wake up a thread blocked by a conditional variable.
The demo is as follows:

 pthread1()
 {
  pthread_mutex_lock(lock_s);
  sum++;
  pthread_mutex_unlock(lock_s);
  if (sum >= 100)
  {
   pthread_cond_signal(&cond_sum_ready);//Send once first
  }
 }
 pthread2()
 {
  pthread_mutex_lock(lock_s);
  while(sum < 100)
  {
   pthread_cond_wait(&cond_sum_ready, &lock_s);//pthread_MUTEX_UNLOCK is executed to unlock and then hibernate
  }
  sum = 0;
  pthread_mutex_unlock(lock_s);
 }

Note: Which thread eventually receives the signal, based on priority

4) Read-write locks: Multiple threads can occupy read-write locks in read mode at the same time, but only one thread can occupy read-write locks in write mode.

  • When a read-write lock is write-locked, all threads attempting to lock the lock will be blocked until the lock is unlocked.
  • When a read-write lock is in a Read-Lock state, other threads can gain access to read mode, but threads that lock it in write mode will be blocked.
  • When a read-write lock is in a read-mode lock state, if other threads attempt to lock in write mode, the read-write lock will usually block subsequent read-mode lock requests, avoiding long-term occupancy of the read-mode lock and long-term blocking of the write mode.
    Read-write locks are useful when data is read more than written.
    API interface:
    Initialization and destruction:
    int pthread_rwlock_init();
    int pthread rwlock_destroy();
    Read lock, write lock, unlock:
    pthread_rwlock_rdlock();
    pthread_rwlock_wrlock();
    pthread_rwlock_unlock();
    Non-blocking access to read and write locks:
    pthread_rwlock_tryrdlock();
    pthread_rwlock_trywrlock();
    demo is as follows
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
​
#define Read_Num 2
​
pthread_rwlock_t lock;
​
class Data
{
    public:
        Data(int i, float f): I(i), F(f){}
        int GetI()
        {
            return I;
        }
​
        float GetF()
        {
            return F;
        }
​
    private:
        int I;
        float F;
};
​
Data *pData = NULL;
​
void *read(void *arg)
{
    int *id = (int*)arg;
    while(true)
    {
        pthread_rwlock_rdlock(&lock);
        printf(" reader %d is reading data\n", *id);
        if (pData == NULL)
        {
            printf("data is NULL\n");
        }
        else
        {
            printf("data: I = %d, F = %f\n", pData->GetI(), pData->GetF());
        }
        pthread_rwlock_unlock(&lock);
    }
    pthread_exit(0);
}
​
void *write(void *arg)
{
    while(true)
    {
        pthread_rwlock_wrlock(&lock); //After writing a lock, all threads attempting to lock the lock will be blocked before unlocking
        printf("writer is wiriting data:\n");
        if (pData == NULL)
        {
            pData = new Data(2, 2.2);
            printf("writer is writing data: %d, %f\n", pData->GetI(), pData->GetF());
        }
        else
        {
            delete pData;
            pData = NULL;
            printf("wirter free the data\n");
        }
        pthread_rwlock_unlock(&lock);
​
    }
    pthread_exit(0);
}
​
int main()
{
    pthread_t reader[Read_Num];
    pthread_t writer;
    for (int i = 0; i < Read_Num; i++)
    {
        pthread_create(&reader[i], NULL, read, (void*)&i);
    }
    pthread_create(&writer, NULL, write, NULL);
    while(1)
    {
        sleep(1);
    }
    return 0;
}

2. The difference between semaphore and mutex:

Mutex locks are used for mutual exclusion, access to resources is out of order, semaphores are used for synchronization, and access to resources is ordered
Mutex locking and unlocking must be used by the same thread, and semaphores can be released by one thread and obtained by another

3. When deadlocks occur and how to avoid them

For example, thread A locks resource M to request resource N;
Thread B locks resource N to apply for resource M;
Deadlock can occur in this case. To avoid it effectively, a banker's algorithm can be used:
Threads A and B use resources in the same order, that is, in the same order when locking, so deadlocks can be avoided.

Posted by hongco on Fri, 21 Jun 2019 09:38:21 -0700