[Linux system programming] Linux thread control primitive

Keywords: Linux

This is the notes of Niuke Linux C + + course.

0. About threads


Note: LWP number is different from thread id. LWP number is the basis for CPU to allocate time slice, and thread id is used to distinguish threads within the process.

1. Difference between thread and process



For processes, the same address (the same virtual address) is used repeatedly in different processes without conflict. The reason is that although their virtual addresses are the same, their page directories, page tables and physical pages are different. The same virtual address is mapped to different physical page memory units, and finally access different physical pages.

But! Different threads! Two threads have their own independent PCBs, but share the same page directory, that is, they share the same page table and physical page. Therefore, two PCBs share an address space.

In fact, whether you create a fork for a process or a pthread for a thread_ Create, the underlying implementation calls the same kernel function clone.
If the address space of the other party is copied, a "process" is output; If you share each other's address space, a "thread" is generated.

Therefore: the Linux kernel does not distinguish between processes and threads. Only at the user level. Therefore, all the thread operation functions pthread_* Is a library function, not a system call.

Advantages: 1. Improve program concurrency 2. Low overhead 3. Convenient data communication and sharing
Disadvantages: 1. The library function is unstable. 2. It is difficult to debug and write. gdb does not support it. 3. It does not support signals well
The advantages are relatively prominent, and the disadvantages are not hard injuries. Under Linux, the process and thread are not very different due to the implementation method.

2. Thread related operation functions

2.1 get thread id

#include <pthread.h>
pthread_t pthread_self(void);

Function: get thread ID. Its function corresponds to the getpid() function in the process.

2.2 create thread: pthread_create

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

Function: create a child thread

Parameters:

  • Thread: outgoing parameter. After the thread is created successfully, the thread ID of the child thread is written to this variable.
  • attr: sets the properties of the thread. Generally, the default property is NULL
  • start_routine: function pointer. This function is the logical code that the sub thread needs to process
  • arg: used for the third parameter (callback function), which is the parameter of the callback function

Return value:

  • Success: 0
  • Failed: error number returned. This error number is different from the previous errno. To get the information of the error number, use:
#include <string.h>
char * strerror(int errnum);

The example code for creating a thread is as follows:

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

void* callback(void* arg) {
    printf("its child thread, thread id is %lu\n", pthread_self());
    printf("arg = %d\n", *(int *)arg);
}

int main()
{
    pthread_t pid;
    int a = 5;
    int ret = pthread_create(&pid, NULL, callback, &a);
    if(ret != 0) {
        // Description creation failed
        char * errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }

    printf("its main thread, thread id is %lu\n", pthread_self());

    sleep(1);

    return 0;
}

Found unable to compile

Review the document and find:

Add - pthread during compilation, and the running results are as follows:

2.3 thread termination: pthread_exit

Note that you cannot use the exit function to terminate the current thread. Exit will terminate the current process and all threads in the process will be terminated together.

#include <pthread.h>
void pthread_exit(void *retval);

Parameter: retval indicates the thread exit status, usually NULL

In a multithreaded environment, you should use pthread instead of the exit function_ Exit function to exit a single thread. Exit in any thread causes the process to exit, and the work of other threads is not finished. When the master thread exits, it cannot return or exit.

2.4 Connection terminated thread (recycle thread): pthread_join

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

Function: connect with a terminated thread (reclaim the resources of child threads)

Note: this function is a blocking function. Only one child thread can be recycled at a time. It is generally used in the main thread

Parameters:

  • Thread: ID of the child thread to be recycled
  • Retval: receives the return value when the child thread exits (that is, the void *retval parameter of pthread_exit), and it is an outgoing parameter.

Return value: 0: successful; Non 0: failed, returned error number

A simple use without outgoing parameters is as follows:

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

void* callback(void* arg) {
    printf("The child thread is running...\n");
    sleep(2);
}

int main()
{
    pthread_t pid;
    int ret = pthread_create(&pid, NULL, callback, NULL);
    if(ret != 0) {
        // Description creation failed
        char * errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }

    pthread_join(pid, NULL);
    printf("Child thread recycled\n");

    return 0;
}

After the sub thread executes for 2 seconds, the main process outputs "the sub thread has been recycled", indicating pthread_ The join function is blocked.

pthread_ What is difficult to understand about the join function is its second parameter: void **retval, which is a void secondary pointer type because:

First, this parameter is to receive pthread_ void *retval passed out by exit. The parameter itself is the first level pointer type of void * and pthread_ The void **retval of the join function is designed as an outgoing parameter during design, so that pthread_exit the outgoing void *retval is brought back to the main thread, so you want to design the void * type variable as an outgoing parameter, that is, void * *.

The sample program is as follows:

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

int value = 10;

void* callback(void* arg) {
    printf("The child thread is running...\n");
    pthread_exit((void *)&value);
}

int main()
{
    pthread_t pid;
    int ret = pthread_create(&pid, NULL, callback, NULL);
    if(ret != 0) {
        // Description creation failed
        char * errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }

    int *thread_retval;  // To pthread_join call to receive pthread_ Outgoing parameters for exit
    pthread_join(pid, (void **)&thread_retval);

    printf("exit data : %d\n", *thread_retval);

    return 0;
}

The operation results are as follows:

2.5 thread separation: pthread_detach

#include <pthread.h>
int pthread_detach(pthread_t thread);

Function: make the process in a separate state. When the separated thread terminates, it will automatically release resources and return them to the system to avoid zombie threads.

Thread separation status: Specifies the status. The thread actively disconnects from the master thread. After a thread ends, its exit state is not obtained by other threads, but directly released automatically by itself. Network and multithreaded servers are commonly used.

Parameter: ID of the thread to be detached

Return value: Success: 0, failure: return error number

be careful:

  1. Threads cannot be separated multiple times, resulting in unpredictable behavior.
  2. If you can't pthread_join a separated thread, an error will be reported: generally, after a thread terminates, its termination status will remain until other threads call pthread_ Join gets its status. However, a thread can also be set to the detach state. Once the thread terminates, it will immediately recover all the resources it occupies without retaining the termination state. Pthread cannot be called on a thread that is already in the detach state_ Join, such a call will return an EINVAL error. That is, if pthread has been called on a thread_ Detach can no longer call pthread_ Join.

2.6 thread cancellation: pthread_cancel

#include <pthread.h>
int pthread_cancel(pthread_t thread);

Function: cancel thread (let thread terminate)

[note]: thread cancellation is not real-time, but has a certain delay. You need to wait for the thread to reach a cancellation point (checkpoint).
Similar to game archiving, you must arrive at the designated place (archiving point, such as inn, warehouse, city, etc.) to store progress. Killing the thread is not done immediately. You must reach the cancellation point.
Cancellation point: it is a place where the thread checks whether it is cancelled and takes action as requested. Usually some system calls creat e, open, pause, close, read, write
Execute the command man 7 pthreads to view the list of system calls with these cancellation points. See also APUE.12.7 cancel options section.
It can be roughly considered that a system call (entering the kernel) is a cancellation point. If there is no cancellation point in the thread, you can set a cancellation point by calling the pthreestcancel function.

Look at the following code example. The child thread loops indefinitely:

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

void* callback(void* arg) {
    while(1) {
        printf("The child thread is running...\n");
        sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t pid;
    int ret = pthread_create(&pid, NULL, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }

    pthread_cancel(pid);
    ret = pthread_join(pid, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }
    printf("Thread recycled\n");
    return 0;
}

After running, it successfully outputs "thread recycled", because pthread_cancel terminates the operation of the child thread, so pthread_join can be executed.

However, if the contents of the loop statement in the subprocess are removed:

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

void* callback(void* arg) {
    while(1) {
        // printf("child thread running... \ n");
        // sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t pid;
    int ret = pthread_create(&pid, NULL, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }

    pthread_cancel(pid);
    ret = pthread_join(pid, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }
    printf("Thread recycled\n");
    return 0;
}

After running, it is found that there is no output and the main thread is blocked. This is because there are no statements in the while(1) loop of the sub thread, so no system calls will be executed and no "cancellation point" will be reached Therefore, the child thread is not terminated, and the main thread is blocked at pthread_join. The printf in the previous code loop statement will call the system call write, so it will reach the "cancellation point", and pthread_join will recycle the terminated child thread.

Posted by php_tom on Sat, 06 Nov 2021 16:50:17 -0700