Introduction to Linux threads (create exit detach join)

1. Thread in process

In the figure above:
On the left is a process with a single thread, which has its own complete set of resources.
On the right is a process with two threads. Threads share resources in the process with each other.
It can be seen that thread is a lightweight process, which provides an efficient way of task processing.

2. Basic interface

2.1 thread creation

Creating a POSIX thread is very simple. You only need to specify the execution function of the thread, but the function interface looks complex. The details are as follows:

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

Parameter Description:
Thread: TID of the new thread
attr: thread attribute. If a standard thread is created, this parameter can be set to NULL
start_routine: thread function
arg: parameter of thread function
start_routine is a function pointer to the thread's execution function. Its parameters and return values are void *. The example code is as follows:
// simpleThread.c

#include <pthread.h>
void *doSomething(void *arg)
{
    // ...
}
int main()
{
    // Create a thread and let it execute the function doSomething()
    pthread_t tid;
    pthread_create(&tid, NULL, doSomething, NULL);
    // ...
}

Various interfaces of threads are separately placed in the thread library. Therefore, when compiling code with threads, you must specify the linked thread library phread, as follows:
gec@ubuntu:~$ gcc simpleThread.c -o simpleThread -lpthread
Concurrency
The most important feature of thread is concurrency. The thread function doSomething() will run simultaneously with the main thread main(), which is the fundamental difference between it and ordinary function calls. It should be noted that due to the concurrency of thread functions, special care should be taken to access shared resources in threads, because these shared resources will be competed by multiple threads to form a "race state". The most typical shared resource is a global variable, such as the following code:
// concurrency.c

#include <pthread.h>
int global = 100;
void *isPrime(void *arg)
{
    while(1)
    {
        // A simple piece of code
        if(global%2 == 0)
            printf("%d It's an even number\n", global);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, isPrime, NULL);
    // A harmless assignment statement for humans and animals
    while(1)
        global = rand() % 5000;
}

The operation results are as follows:

gec@ubuntu:~$ ./concurrency
4383 It's an even number
2777 It's an even number
492 It's an even number
492 It's an even number
2362 It's an even number
3690 It's an even number
59 It's an even number
3926 It's an even number
540 It's an even number
3426 It's an even number
4172 It's an even number
211 It's an even number
368 It's an even number
2567 It's an even number
1530 It's an even number
1530 It's an even number
2862 It's an even number
4067 It's an even number
...
gec@ubuntu:~$ 

It can be seen that there are many errors and omissions in the results. The reason is that because of the concurrency between threads, the global will be scrambled at any time. In this case, when multiple threads or processes access shared resources at the same time, they must be constrained by synchronous mutual exclusion mechanisms such as mutual exclusion lock, read-write lock and conditional quantity========

2.2 thread exit

Similar to a process, when a thread finishes executing its task, it can use the following interface to exit:

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

Where, the parameter retval is the return value of the thread and the return value of the function executed by the corresponding thread. If the thread has no data to return, it can be written as NULL.
Note the difference between this function and exit:
pthread_exit(): exit the current thread
exit(): exit the current process (that is, exit all threads in the process)
Each thread in a process runs parallel and concurrently. The thread running the main function main() is called the main thread. The main thread can exit before other threads, such as:

#include <pthread.h>
void *count(void *arg)
{
    // Cycle count
    for(int i=0; ;i++)
    {
        printf("%d\n", i);
        usleep(200*1000);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, count, NULL);
    // The main route exits first
    pthread_exit(NULL);
}

After the main thread exits, other threads can continue to run, but please note that in the above code, if the main thread does not call pthread_ If exit (), it is equivalent to exiting the whole process, and the child thread will also be forced to exit.

2.3 thread engagement

Similar to a process, a thread will not immediately release its system resources after exiting, but will become a zombie thread. Other threads can use pthread_join() to release the resources of the zombie thread and obtain the exit value returned when it exits. This interface function is called the thread join function:

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

Interface Description:
If the thread of the specified tid has not exited, the function will continue to block.
If you only want to block and wait for the specified thread tid to exit without its exit value, val can be set to NULL.
If the thread of the specified tid is in a detached state or does not exist, the function returns an error.
It should be noted that all threads, including the main thread, have equal status. Any thread can exit first, and any thread can join another thread. The following is a simple application example of join function:

#include <pthread.h>
void *routine(void *arg)
{
    pthread_exit("abcd");
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, routine, NULL);
    // Attempt to join a child thread and get its exit value
    void *val;
    pthread_join(tid, &val);
    printf("%d\n", (char *)val);
}

2.4 other thread API s

2.4. 1 get thread TID

The following interface can obtain the ID number of the thread:

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

The above interface is similar to getpid() in process management. It should be noted that the PID of the process is the global resource of the system, and the TID of the thread is only valid between threads within the process. When we want to perform operations on a thread, such as sending signals, canceling, blocking joints, etc., we need to use the thread ID.

2.4. 2 thread error code

The processing of the system error code by the thread function is very different from that of the standard C library function. The standard C library function will set the global error code errno, and the thread function will directly return the error code when an error occurs.
Take thread joining as an example. To determine whether the joining is successful, output the exit value of zombie thread in case of success and the reason for failure in case of failure, the implementation code should be written as follows:

void *val;
errno = pthread_join(tid, &val);
if(errno == 0)
    printf("Successfully joined the thread, and its exit value is:%ld", (long)val);
else
    printf("Join thread failed:%s\n", strerror(errno)); // Note that the header file string h
 Or:
void *val;
errno = pthread_join(tid, &val);
if(errno == 0)
    printf("Successfully joined the thread, and its exit value is:%d", (int)val);
else
    perror("Join thread failed");

All in pthread_ For thread functions starting with XXX, 0 is returned for success and error code is returned for failure.

2.4. 3 function singleton

Consider this scenario:
Suppose a program contains multiple threads, These threads use semaphores (whether system-V semaphores or POSIX semaphores) for cooperation. Because semaphores must be initialized before use, in order to optimize program performance, we hope that whoever runs fast when threads start will initialize the semaphores, and ensure that the initialization work is strictly executed.

#include <pthread.h>
// Function singleton control variable
pthread_once_t once_control = PTHREAD_ONCE_INIT;
// Function singleton startup interface
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));

Interface Description:
once_control is a special variable used to associate a function singleton. The associated function singleton will only be executed once.
init_ The function pointed to by the routine function pointer is a function singleton that is executed only once.
The following is sample code:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
// Function singleton control variable
pthread_once_t once_control = PTHREAD_ONCE_INIT;
void init_routine(void)
{
    printf("I'll be strictly enforced.\n");
}
void *f(void *arg __attribute__((unused)))
{
    pthread_once(&once_control, init_routine);
    pthread_exit(NULL);
}
int main()
{
    pthread_t tid;
    for(int i=0; i<20; i++)
    	pthread_create(&tid, NULL, f, NULL);
    pthread_exit(NULL);
}

No need to pay attention to useful

Keywords: Linux Operation & Maintenance Multithreading

Added by kalpesh on Tue, 28 Dec 2021 15:03:40 +0200