Linux system application programming -- thread recycling of multithreading

Keywords: Linux

Like a process, when a child thread exits, its kernel resources are mainly recycled by the main thread. The thread recycling function provided in the thread library is called pthread_join() is a blocking function. If there are still sub threads running, calling this function will block. The sub thread exits the function to unblock and recycle resources. When the function is called once, only one sub thread can be recycled. If there are multiple sub threads, recycling is required.

In addition, the thread recycling function can also obtain the data passed when the child thread exits. The function prototype is as follows:

#include <pthread.h>
// This is a blocking function. When a child thread runs this function, it will block
// When the sub thread exits, the function unblocks and reclaims the corresponding sub thread resources, which is similar to the function wait() used by the recycling process
int pthread_join(pthread_t thread, void **retval);

Parameters:

thread: The thread of the child thread to be recycled ID

retval: The secondary pointer, which points to the address of the primary pointer, is an outgoing parameter, which is stored in this address pthread_exit () If this parameter is not required, the passed data can be specified as NULL
 Return value: 0 is returned for successful thread recycling, and the error number is returned for failed recycling.

1. Recycle sub thread data

Pthread can be used when the child thread exits_ The parameter of exit () transfers the data out. When recycling this sub thread, you can use phread_ The second parameter of join () to receive the data passed by the child thread. There are many processing methods for received data, which are listed below:

1.1 using sub thread stack

Through function pthread_exit(void retval); It can be seen that when the sub thread exits, it needs to record the data into a piece of memory. The address of the memory where the data is stored is transmitted through the parameter, not the specific data. Because the parameter is of void type, all this universal pointer can point to any type of memory address. Let's look at the first method. Save the child thread exit data in the child thread's own stack area:

// pthread_join.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

// Define structure
struct Persion
{
    int id;
    char name[36];
    int age;
};

// Processing code of child thread
void* working(void* arg)
{
    printf("I'm a child thread, thread  ID: %ld\n", pthread_self());
    for(int i=0; i<9; ++i)
    {
        printf("child == i: = %d\n", i);
        if(i == 6)
        {
            struct Persion p;
            p.age  =12;
            strcpy(p.name, "tom");
            p.id = 100;
            // The parameters of this function pass this address to the pthread of the main thread_ join()
            pthread_exit(&p);
        }
    }
    return NULL;	// If the code can't execute to this position, it exits
}

int main()
{
    // 1. Create a child thread
    pthread_t tid;
    pthread_create(&tid, NULL, working, NULL);

    printf("The child thread was created successfully, thread  ID: %ld\n", tid);
    // 2. The sub thread will not execute the following code, but the main thread will execute it
    printf("I'm the main thread, thread  ID: %ld\n", pthread_self());
    for(int i=0; i<3; ++i)
    {
        printf("i = %d\n", i);
    }

    // Block waiting for child thread to exit
    void* ptr = NULL;
    // ptr is an outgoing parameter, which points to a valid memory inside the function
    // The memory address is pthread_ The memory pointed to by the exit() parameter
    pthread_join(tid, &ptr);
    // Print information
    struct Persion* pp = (struct Persion*)ptr;
    printf("The child thread returns data: name: %s, age: %d, id: %d\n", pp->name, pp->age, pp->id);
    printf("The child thread resource was successfully reclaimed...\n");
    
    return 0;
}

Compile output:

NOTE:
Through the printed log, it can be found that the data information returned by the child thread is not obtained in the main thread. The specific reasons are as follows:

If multiple threads share the same virtual address space, each thread has its own memory in the stack area, which is equivalent to that the stack area is divided equally by these threads. When the thread exits, the memory of the thread in the stack area will be recycled. Therefore, with the exit of the sub thread, the data written into the stack area will be released.

1.2 using global variables

Threads in the same virtual address space cannot share stack data, but can share global data area and heap data. Therefore, when the sub thread exits, the outgoing data can be stored in global variables, static variables or heap memory. In the following example, the data is stored in the global variable:

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

// Define structure
struct Persion
{
    int id;
    char name[36];
    int age;
};

struct Persion p;	// Define global variables

// Processing code of child thread
void* working(void* arg)
{
    printf("I'm a child thread, thread  ID: %ld\n", pthread_self());
    for(int i=0; i<9; ++i)
    {
        printf("child == i: = %d\n", i);
        if(i == 6)
        {
            // Use global variables
            p.age  =12;
            strcpy(p.name, "tom");
            p.id = 100;
            // The parameters of this function pass this address to the pthread of the main thread_ join()
            pthread_exit(&p);
        }
    }
    return NULL;
}

int main()
{
    // 1. Create a child thread
    pthread_t tid;
    pthread_create(&tid, NULL, working, NULL);

    printf("The child thread was created successfully, thread  ID: %ld\n", tid);
    // 2. The sub thread will not execute the following code, but the main thread will execute it
    printf("I'm the main thread, thread  ID: %ld\n", pthread_self());
    for(int i=0; i<3; ++i)
    {
        printf("i = %d\n", i);
    }

    // Block waiting for child thread to exit
    void* ptr = NULL;
    // ptr is an outgoing parameter, which points to a valid memory inside the function
    // The memory address is pthread_ The memory pointed to by the exit() parameter
    pthread_join(tid, &ptr);
    // Print information
    struct Persion* pp = (struct Persion*)ptr;
    printf("name: %s, age: %d, id: %d\n", pp->name, pp->age, pp->id);
    printf("The child thread resource was successfully reclaimed...\n");
    
    return 0;
}

Compile output:
You can see that the data is indeed passed to the main thread through the child thread and through the global variable

1.3 using mainline stack

Although each thread has its own stack space, multiple threads in the same address space can access each other's stack space data. In many cases, it is necessary to recover the sub thread resources in the main thread, so the main thread generally exits last. For this reason, the data returned by the sub thread is saved to the stack memory of the main thread in the following program:

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

// Define structure
struct Persion
{
    int id;
    char name[36];
    int age;
};


// Processing code of child thread
void* working(void* arg)
{
    struct Persion* p = (struct Persion*)arg;
    printf("I'm a child thread, thread  ID: %ld\n", pthread_self());
    for(int i=0; i<9; ++i)
    {
        printf("child == i: = %d\n", i);
        if(i == 6)
        {
            // Use the stack memory of the main thread
            p->age  =12;
            strcpy(p->name, "tom");
            p->id = 100;
            // The parameters of this function pass this address to the pthread of the main thread_ join()
            pthread_exit(p);
        }
    }
    return NULL;
}

int main()
{
    // 1. Create a child thread
    pthread_t tid;

    struct Persion p;
    // The stack memory of the main thread is passed to the child thread
    pthread_create(&tid, NULL, working, &p);

    printf("The child thread was created successfully, thread  ID: %ld\n", tid);
    // 2. The sub thread will not execute the following code, but the main thread will execute it
    printf("I'm the main thread, thread  ID: %ld\n", pthread_self());
    for(int i=0; i<3; ++i)
    {
        printf("i = %d\n", i);
    }

    // Block waiting for child thread to exit
    void* ptr = NULL;
    // ptr is an outgoing parameter, which points to a valid memory inside the function
    // The memory address is pthread_ The memory pointed to by the exit() parameter
    pthread_join(tid, &ptr);
    // Print information
    printf("name: %s, age: %d, id: %d\n", p.name, p.age, p.id);
    printf("The child thread resource was successfully reclaimed...\n");
    
    return 0;
}

Compile output:

In the above procedure, call pthread_. Create() creates a child thread, passes the address of the stack space variable p in the main thread to the child thread, and writes the data to be passed to this memory in the child thread. In other words, in the main() function of the program, the data from the sub thread can be read out through the pointer variable ptr or the structure variable P.

Posted by kevinkhan on Mon, 29 Nov 2021 07:59:04 -0800