Multithreaded programming

Posted by xiledweb on Mon, 27 Sep 2021 10:14:29 +0200

11. Mutex
initiate static
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;      The memory unit is not released until the process is completed
dynamic initialization
pthread_mutex_t mutex; // Define mutex objects
pthread_ mutex_ init(&mutex, NULL); // Allocate kernel resources
...
pthread_ mutex_ destroy(&mutex); // Free kernel resources
...
pthread_ mutex_ lock(&mutex); // Lock mutex
At any time, only one thread will successfully lock a specific mutex, and other threads trying to lock it will block and wait in this function until the holder thread of the mutex unlocks it.
pthread_ mutex_ unlock(&mutex); // Unlock mutex
Threads that have successfully locked a specific mutex object can unlock it through this function. One of those threads blocking the attempt to lock the mutex object will be awakened to obtain the mutex and get the mutex from pthread_ mutex_ Returned in the lock function.
void* thread1(void* arg) {
    ...
    pthread_mutex_lock(&mutex);
    Perform operations to access shared objects
    pthread_mutex_unlock(&mutex);
    ...
}
void* thread2(void* arg) {
    ...
    pthread_mutex_lock(&mutex);
    Perform operations to access shared objects
    pthread_mutex_unlock(&mutex);
    ...
}
Code: mutex1.c, mutex2.c

/* mutex1.c */
#include <stdio.h>
#include <string.h>
#include <pthread.h>
unsigned int g = 0;
//pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t m;
void* thread_proc(void* arg) {
    //pthread_mutex_lock(&m);
    for (unsigned int i = 0; i < 100000000; ++i){
        pthread_mutex_lock(&m);
        ++g;
        pthread_mutex_unlock(&m);
    }
    //pthread_mutex_unlock(&m);
    return NULL;
}
int main(void) {
    pthread_mutex_init(&m, NULL);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc, NULL);
    pthread_create(&t2, NULL, thread_proc, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&m);
    printf("g = %u\n", g);
    return 0;
}


/* mutex2.c */
#include <stdio.h>
#include <string.h>
#include <pthread.h>
int g = 0;
//pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t m;
void* thread_proc1(void* arg) {
    for (unsigned int i = 0; i < 10000000; ++i){
        pthread_mutex_lock(&m);
        ++g;
        pthread_mutex_unlock(&m);
    }
    return NULL;
}
void* thread_proc2(void* arg) {
    for (unsigned int i = 0; i < 10000000; ++i){
        pthread_mutex_lock(&m);
        --g;
        pthread_mutex_unlock(&m);
    }
    return NULL;
}
int main(void) {
    pthread_mutex_init(&m, NULL);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc1, NULL);
    pthread_create(&t2, NULL, thread_proc2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&m);
    printf("g = %d\n", g);
    return 0;
}

Mutex not only ensures data consistency and prevents concurrency conflict, but also sacrifices the concurrency of multithreaded applications. Therefore, mutex should be used as little as possible in design, or only in the operation process that needs to be protected by mutex, but not in cases where the protection requirements are not obvious.

12. Semaphore
The function and usage are very similar to the semaphore set in the XSI/IPC object, but the semaphore set in the XSI/IPC object can only be used for processes, and the semaphore here can be used for both processes and threads.
#include <semphore.h>
Initialization semaphore
int sem_init(sem_t* sem, int pshared, unsigned int value);
0 is returned for success and - 1 is returned for failure.
sem - semaphore.
pshared - 0 indicates that the semaphore is used for threads, and non-0 indicates that the semaphore is used for processes.
Value - the initial value of the semaphore, that is, the initial number of free resources.
When the pshared parameter is 0, the semaphore is only used for multiple threads in a process, which is essentially a common global variable. However, if the parameter is changed to non-0, the semaphore can be used for different processes, and its storage location is in the shared memory that can be accessed by these processes.
Destroy semaphore
int sem_destroy(sem_t* sem);
0 is returned for success and - 1 is returned for failure.
sem - semaphore.
Waiting semaphore
int sem_wait(sem_t* sem);
0 is returned for success and - 1 is returned for failure.
If the current value of the semaphore is greater than 0, it is subtracted by 1 and immediately returned to 0, indicating that the resource is obtained. If the current value of the semaphore is equal to 0, it means that there are no free resources to allocate, and the thread or process calling the function will be blocked until the value of the semaphore released by the resource is reduced by 1.
Release semaphore
int sem_post(sem_t* sem);
0 is returned for success and - 1 is returned for failure.
Add 1 to the value of the semaphore. Those are blocking the SEM for this semaphore_ A thread or process in the wait function call will be awakened and removed from SEM after subtracting 1 from the semaphore_ Returned in the wait function.
Gets the current value of the semaphore
int sem_getvalue(sem_t* sem, int* sval);
0 is returned for success and - 1 is returned for failure.
sem - semaphore
sval - the current value of the output semaphore, that is, the current number of allocable resources
Code: sem.c

/* sem.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX_ Conns 5 / / maximum connections
#define MAX_USERS 50 / / maximum number of users
sem_t s;
void* thread_user(void* arg) {
    pthread_t tid = pthread_self();
    int sval;
    sem_getvalue(&s, &sval);
    printf("%lu Thread: waiting for data connection(Remaining%d Free connections)...\n", tid, sval);
    sem_wait(&s);
    sem_getvalue(&s, &sval);
    printf("%lu Threads: getting data connections(Remaining%d Free connections)...\n", tid, sval);
    usleep(1000000);
    sem_post(&s);
    sem_getvalue(&s, &sval);
    printf("%lu Threads: releasing data connections(Remaining%d Free connections)...\n", tid, sval);
    return NULL;
}
int main(void) {
    pthread_t tids[MAX_USERS];
    sem_init(&s, 0, MAX_CONNS);
    for (int i = 0; i < sizeof(tids) / sizeof(tids[0]); ++i)
        pthread_create(&tids[i], NULL, thread_user, NULL);
    for (int i = 0; i < sizeof(tids) / sizeof(tids[0]); ++i)
        pthread_join(tids[i], NULL);
    sem_destroy(&s);
    return 0;
}

Mutexes can be regarded as semaphores with an initial value of 1

13. Deadlock
Code: dl.c

/* dl.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t b = PTHREAD_MUTEX_INITIALIZER;
void* thread_proc1(void* arg) {
    printf("Thread 1: lock a...\n");
    pthread_mutex_lock(&a);
    printf("Thread 1: lock a success!\n");
    sleep(1);
    printf("Thread 1: lock b...\n");
    pthread_mutex_lock(&b);
    printf("Thread 1: lock b success!\n");
    // ...
    pthread_mutex_unlock(&b);
    printf("Thread 1: Unlock b. \n");
    pthread_mutex_unlock(&a);
    printf("Thread 1: Unlock a. \n");
    return NULL;
}
void* thread_proc2(void* arg) {
    printf("Thread 2: locking b...\n");
    pthread_mutex_lock(&b);
    printf("Thread 2: locking b success!\n");
    sleep(1);
    printf("Thread 2: locking a...\n");
    pthread_mutex_lock(&a);
    printf("Thread 2: locking a success!\n");
    // ...
    pthread_mutex_unlock(&a);
    printf("Thread 2: Unlock a. \n");
    pthread_mutex_unlock(&b);
    printf("Thread 2: Unlock b. \n");
    return NULL;
}
int main(void) {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_proc1, NULL);
    pthread_create(&t2, NULL, thread_proc2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("Done!\n");
    return 0;
}

Four necessary conditions for Deadlock:
1) Exclusive and exclusive: a thread uses the resource it obtains exclusively, that is, other threads are not allowed to use the resource for a period of time. During this time, any thread trying to request the resource can only wait in the block until the resource is actively released by its owner.
2) Request hold: a thread already has at least one resource, but tries to obtain the resources owned by other threads. Therefore, it can only wait in the block and stick to the resources it has obtained.
3) Inalienable: the resources obtained by a thread cannot be forcibly deprived before they are used up, but can only be released by its owner.
4) Circular waiting: in the thread set {T0,T1,Tn}, t0 waits for the resources occupied by T1, T1 waits for the resources occupied by T2,..., and Tn waits for the resources occupied by t0 to form a loop.

14. Conditional variables
Thread 1
    ...
    if (the conditions on which subsequent operations depend are not met)
        Sleep depends on conditional variables;
    Subsequent operation
    ...
Thread 2
    ...
    Create the conditions on which subsequent operations depend
    Wake up the thread sleeping in the condition variable
    ...
initiate static
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
dynamic initialization
pthread_cond_init(&cond, NULL);
...
pthread_cond_destroy(&cond);
Wait for the condition variable, that is, sleep in the condition variable
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
When the calling thread sleeps in the cond condition variable, mutex mutex will be unlocked until the thread wakes up from the condition variable, that is, it returns from the function and then owns the mutex again.
int pthread_cond_signal(pthread_cond_t* cond);
Wake up the first thread sleeping in the condition variable cond, that is, the first thread of the condition waiting queue. After it regains the mutex, it starts from pthread_ cond_ Returned in the wait function.
int pthread_cond_broadcast(pthread_cond_t* cond);
Wake up all the threads sleeping in the condition variable cond, and only the thread that regains the mutex will be from pthread_ cond_ Returned in the wait function.

Producer consumer model:
Hardware / Network - > producer thread - Data - > consumer thread
                             \__________________/
                                        Rigid coupling
In this case, the producer thread rate will affect the consumer thread rate, resulting in a decrease in the execution rate of the whole thread, which can be solved through flexible coupling.

Hardware / Network - > producer thread - Data - > buffer - Data - > consumer thread
                             \________________________________/
                                              Flexible coupling
Ideal buffer: always dissatisfied and not empty
Actual buffer: full can't be put - hold to death, empty can't be extracted - starve to death.

Producer thread:
    if (buffer full)
        Sleep in the non full condition variable and release the buffer lock
        After being awakened, leave the conditional waiting queue and regain the buffer lock
    Production - > non empty
    Wakes up a consumer thread sleeping in a non null condition variable
Consumer thread:
    if (buffer empty)
        Sleep in a non null condition variable and release the buffer lock
        After being awakened, leave the conditional waiting queue and regain the buffer lock
    Consumption - > not full
    Wake up the producer thread sleeping in the non full condition variable
Code: cond.c

/* cond.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#define MAX_STOCK 5 / / warehouse capacity
char storage[MAX_STOCK]; // Warehouse
int stock = 0; // Current inventory
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
// Non full conditional variable
pthread_cond_t full = PTHREAD_COND_INITIALIZER;
// Non null conditional variable
pthread_cond_t empty = PTHREAD_COND_INITIALIZER;
// Display inventory
void show(char const* who, char const* op,
    char prod) {
    printf("%s: ", who);
    for (int i = 0; i < stock; ++i)
        printf("%c", storage[i]);
    printf("%s%c\n", op, prod);
}
// Producer thread
void* producer(void* arg) {
    char const* who = (char const*)arg;
    for (;;) {
        pthread_mutex_lock(&mutex);
        while (stock == MAX_STOCK) {
            printf("\033[;;32m%s: Man Cang!\033[0m\n", who);
            pthread_cond_wait(&full, &mutex);
        }
        char prod = 'A' + rand() % 26;
        show(who, "<-", prod);
        storage[stock++] = prod;
        //pthread_cond_signal(&empty);
        pthread_cond_broadcast(&empty);
        pthread_mutex_unlock(&mutex);
        usleep((rand() % 100) * 1000);
    }
    return NULL;
}
// Consumer thread
void* customer(void* arg) {
    char const* who = (char const*)arg;
    for (;;) {
        pthread_mutex_lock(&mutex);
        while (stock == 0) {
            printf("\033[;;31m%s: Empty!\033[0m\n", who);
            pthread_cond_wait(&empty, &mutex);
        }
        char prod = storage[--stock];
        show(who, "->", prod);
        //pthread_cond_signal(&full);
        pthread_cond_broadcast(&full);
        pthread_mutex_unlock(&mutex);
        usleep((rand() % 100) * 1000);
    }
    return NULL;
}
int main(void) {
    srand(time(NULL));
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_t tid;
    pthread_create(&tid, &attr, producer, "Producer 1");
    pthread_create(&tid, &attr, producer, "Producer 2");
    pthread_create(&tid, &attr, customer, "Consumer 1");
    pthread_create(&tid, &attr, customer, "Consumer 2");
    getchar();
    return 0;
}

Topics: C Multithreading Operating System lock linus