Linux multithreaded programming

Keywords: Linux

Thread synchronization: in a multithreaded environment, we can get the desired results regardless of the scheduling order
Synchronization methods: semaphores, mutexes, conditional variables, read-write locks
Mutex can only be used in mutex scenarios, and its function is equivalent to that of binary (0 / 1) semaphores

pthread_create: creates and starts a thread
pthread_exit: exits the thread

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

// The return value and parameter of the thread function are void*
void* fun(void* arg){
    for(int i = 0; i < 5; i++){
        printf("fun run\n");
        sleep(1);
    }
}

int main(){
    pthread_t id;
    // Thread id, thread attribute, thread execution function, function parameter success, return 0
    int res = pthread_create(&id, NULL, fun, NULL);
    if(res != 0){
        printf("create thread error!\n");
        exit(0);
    }
    for(int i = 0; i < 2; i++){
        printf("main run\n");
        sleep(1);
    }
    // exit(0); // Exit process
    pthread_exit(NULL); // Exit thread
}

Generally speaking, the exit of the main thread does not affect the child threads, only because exit will be called automatically at the end of the main function, resulting in the exit of the whole process and the end of all threads.

pthread_join: wait for the thread to end

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

// The return value and parameter of the thread function are void*
void* fun(void* arg){
    for(int i = 0; i < 5; i++){
        printf("fun run\n");
        sleep(1);
    }
    pthread_exit("fun over!\n");
}

int main(){
    pthread_t id;
    // Thread id, thread attribute, thread execution function, function parameter success, return 0
    int res = pthread_create(&id, NULL, fun, NULL);
    if(res != 0){
        printf("create thread error!\n");
        exit(0);
    }
    for(int i = 0; i < 2; i++){
        printf("main run\n");
        sleep(1);
    }
    // exit(0); // Exit process
    // pthread_exit(NULL); //  Exit thread
    char* info = NULL;
    pthread_join(id, (void**)&info); // Wait for id thread to end
    printf("%s\n", info);
    exit(0);
}

Analyze print results

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

// The return value and parameter of the thread function are void*
void* fun(void* arg){
    int index = *(int*)arg;
    for(int i = 0; i < 3; i++){
        printf("index = %d\n", index);
        sleep(1);
    }
}

int main(){
    pthread_t id[5];
    // Thread id, thread attribute, thread execution function, function parameter success, return 0
    for(int i = 0; i < 5; i++){
        pthread_create(&id[i], NULL, fun, &i);
    }
    
    for(int i = 0; i < 5; i++){
        // The return value is not received and NULL is passed
        pthread_join(id[i], NULL);
    }

    exit(0);
}


In the program, the main thread creates five sub threads and passes the address of variable i to fun. However, when the sub thread obtains the value from the address of variable i, the value at this time may have been changed by the main thread (the changed value must be greater than the value when the thread is created), which is no longer the value passed in when the thread is created.

Print results ≤ 5000 \leq5000 ≤5000

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

int g_val = 0;

void* fun(void* arg){
    for(int i = 0; i < 1000; i++){
        g_val++;
    }
}

int main(){
    pthread_t id[5];
    // Thread id, thread attribute, thread execution function, function parameter success, return 0
    for(int i = 0; i < 5; i++){
        pthread_create(&id[i], NULL, fun, NULL);
    }
    
    for(int i = 0; i < 5; i++){
        // The return value is not received and NULL is passed
        pthread_join(id[i], NULL);
    }
    printf("g_val = %d\n", g_val);
    exit(0);
}

On machines with multi-core processors, it may occur that one thread reads data and adds 1 before it has time to write back. Another thread also reads and writes back in advance, which leads to the final result of less than 5000

Implementation of Linux Process

Non thread safe function strtok()

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

// The return value and parameter of the thread function are void*
void* fun(void* arg){
    char arr[] = {"1 2 3 4 5 6"};
    char* s = strtok(arr, " ");
    while(s != NULL){
        printf("fun s = %s\n", s);
        sleep(1);
        s = strtok(NULL, " ");
    }
}

int main(){
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);

    char str[] = {"a b c d e f"};
    char* p  = strtok(str, " ");
    while(p != NULL){
        printf("main p = %s\n", p);
        sleep(1);
        p = strtok(NULL, " ");
    }
    pthread_join(id, NULL);
    exit(0);
}

Operation results:

main p = a
fun s = 1
fun s = 2
main p = 3
main p = 4
fun s = 5
fun s = 6
main p = 6

There is a problem: letters are followed by numbers

What causes non thread safe functions?
A global or static variable is used in a function

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

// The return value and parameter of the thread function are void*
void* fun(void* arg){
    char arr[] = {"1 2 3 4 5 6"};
    char* ptr = NULL; // Record where the last split was
    char* s = strtok_r(arr, " ", &ptr);
    while(s != NULL){
        printf("fun s = %s\n", s);
        sleep(1);
        s = strtok_r(NULL, " ", &ptr);
    }
}

int main(){
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);

    char str[] = {"a b c d e f"};
    char* ptr = NULL;
    char* p  = strtok_r(str, " ", &ptr);
    while(p != NULL){
        printf("main p = %s\n", p);
        sleep(1);
        p = strtok_r(NULL, " ", &ptr);
    }
    pthread_join(id, NULL);
    exit(0);
}

Operation results:

main p = a
fun s = 1
main p = b
fun s = 2
fun s = 3
main p = c
fun s = 4
main p = d
fun s = 5
main p = e
main p = f
fun s = 6

Multi process and multi process mixing

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

void* fun(void* arg){
    for(int i = 0; i < 50; i++){
        printf("fun run pid = %d\n", getpid());
        sleep(1);
    }
}

int main(){
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);
    
    fork();
    for(int i = 0; i < 50; i++){
        printf("main run pid = %d\n", getpid());
        sleep(1);
    }
    exit(0);
}

ps -eLf: view process and thread information

The child process copies all the resources of the parent process (semaphores and lock states are the same as those of the parent process at the beginning), but the child process starts execution wherever it is copied, and the threads owned by the parent process will not be copied. In a word, when a child process is generated, there is only one thread in the process.

If you must use multiple processes in a multithreaded environment, you must fork when no thread uses a lock. Otherwise, the state of the lock is not clear when forking, and the child process cannot be used normally.

Note: you can't ignore the three, seven and twenty-one. The child process will execute un lock to avoid some resources being preempted by threads at will.

Solution: use pthread_ At fork, lock before fork, and unlock both parent and child processes after fork

void prepare(void){
    pthread_mutex_lock(&mutex);
}

void parent(void){
    pthread_mutex_unlock(&mutex);
}

void child(void){
    pthread_mutex_unlock(&mutex);
}

pthread_atfork(prepare, parent, child);

Posted by nolos on Fri, 19 Nov 2021 10:07:54 -0800