Multi-threaded programming--Thread common functions

1. Threads and processes

Process: A program that is being executed is the smallest unit of resource allocation

1) Things in a process need to be executed one by one in a certain order, so how to make some things in a process execute at the same time?

2) The process has many disadvantages: first, because the process is the resource owner, there is a large time and space overhead in creation, cancellation and switching, so it is necessary to introduce a lightweight process; second, due to the emergence of multi-processor (SMP), it can meet Multiple running units, and multiple processes parallel overhead is too large.

Thread: Also known as a lightweight process, the smallest unit of program execution, the basic unit of system scheduling and allocation of cpu , he is an entity in the process. There can be multiple threads in a process, and these threads share all the resources of the process, and the threads themselves only contain a few essential resources.

The process is responsible for applying for resources, and it is a thread from the main function.

The process applies for resources, and each thread in the multi-thread shares the resources in the thread

Processes apply for resources, and multiple processes use the fork() function to create child processes

1. Fork copy will consume resources

2. Inter-process communication also needs to go through pipelines, message queues, and semaphores to communicate more complicated

The difference between process and thread:

1) A process has its own independent address space, and multiple threads share the same address space

2) Threads save system resources more

3) Shared by multiple threads in one address space, each thread has its own stack area

4) Multiple threads in each address space are exclusive, and the code area, heap area, data area, and open files (file descriptors) are all shared by threads

5) Each process corresponds to a virtual address space, and a process can only grab one CPU time slice

6) Multiple threads can be divided in one address space, and more CPU time slices can be grabbed on the basis of effective resources

CPU scheduling and switching: thread context switching is much faster than process

Context switching: process/thread multiplexes CPU time slices in time-sharing. Before switching, the state of the last task will be saved. When switching back to this task next time, this state will be loaded and continue to execute. The task is from saving to reloading . A process is a context switch .

The process is cheaper, faster to start, and faster to exit, with less impact on system resources

Using multithreading is more advantageous than using multiprocess when dealing with multitasking programs, but it does not mean that the more threads, the better.

2. Some terminology of threads

1. Concurrency means that at the same time, only one instruction is executed, but multiple process instructions are quickly executed in rotation, so that there is a macroscopic effect of simultaneous execution of multiple processes. appears to be happening simultaneously, for single-core processors

2. Parallelism means that at the same time, multiple instructions are executed on multiple processors (cpu) at the same time. true simultaneity

3. Synchronization: Calls that are dependent on each other should not "happen at the same time", and synchronization is to prevent those "simultaneous" things

4. Asynchronous: The concept of asynchronous is opposite to synchronous. Any two independent operations are asynchronous, which means that things happen independently

Advantages of multithreading:

1. Parallelism in developing programs in multiprocessors

2. While waiting for IO operations, the program can perform other operations to improve concurrency

3. Modular programming can more clearly express the relationship between independent events in the program, and the structure is clear

4. Occupies less system resources, compared to multi-process

5. Multi-threading does not necessarily require multi-processors, multi-processors only improve parallelism

3. Thread creation function

The child thread is created. When a thread is created in a single-process program, the process degenerates into the main thread. The identifier types of threads and processes, the function to obtain id, and the thread creation function are shown in the following figure:

The pthread_create function is used as follows:

#include<pthread.h>

int  pthread_create(pthread_t *tidp, const  pthread_attr_t *attr,
                            ( void *)(*start_rtn)( void *), void  *arg);
                           
参数说明:
thread:传出参数、是无符号长整型数,会将线程id写到这个指针指向的内存中
attr:线程属性,一般为空
start_rtn:是线程运行函数的起始地址         
arg:运行函数的参数     

返回值:
线程创建成功,则返回0,创建失败返回错误参数   

编译方法:
-lpthread //pthread为动态库
例如:
gcc test.c -lpthread -o test

Test demo:

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

void print_id(char *s)
{
        printf("%s :pid is %u tid is %ld\n", s, getpid(), pthread_self());
}

void* callback(void *arg)
{
        print_id(arg);
        for (int i = 0; i < 5; i++) {
                printf("子线程: i = %d\n", i);
        }
        printf("子线程: %ld\n", pthread_self());

        return (void *)0;
}

int main()
{
        pthread_t tid;
        pthread_create(&tid, NULL, callback, "new thread");
        for (int i = 0; i < 5; i++) {
                printf("主线程: i = %d\n", i);
        }

        printf("pid is %u tid is %ld\n", getpid(), pthread_self());
        printf("主线程: %ld\n", pthread_self());
        // sleep(2);
        
        return 0;
}

Execution result one

主线程: i = 0
主线程: i = 1
主线程: i = 2
主线程: i = 3
主线程: i = 4
pid is 23801 tid is 139896767649536
主线程: 139896767649536

When we open the comment of sleep(2), the execution result 2 is as follows:

主线程: i = 0
主线程: i = 1
主线程: i = 2
主线程: i = 3
主线程: i = 4
pid is 23892 tid is 140477647664896
主线程: 140477647664896
new thread :pid is 23892 tid is 140477639358208
子线程: i = 0
子线程: i = 1
子线程: i = 2
子线程: i = 3
子线程: i = 4
子线程: 140477639358208

explain:

The program starts to execute from the main function. When the pthread_create function is executed, a callback sub-thread will be created to execute the relevant code in the callback function. At the same time, the main function will continue to execute downward. After the main function is executed, the relevant virtual address space will be released. Resources, the callback sub-thread has not finished running at this time, and it will be executed at this time and result one will appear. When we sleep(2); in the main thread, the life cycle of the virtual address space can be extended, and the relevant content of the sub-thread can be executed normally.

4. Thread exit function

When writing a multi-threaded program, if we want the thread to exit without releasing the virtual address space resources, we can call the thread exit function in the thread library. As long as the function is called, the current thread will exit immediately, and will not It affects the normal operation of other threads, whether it is a child thread or the main thread.

#include <pthread.h>

void pthread_exit(void *retval);

参数说明:
retval:void*类型的指针,指向的数据将作为线程退出时的返回值。如果线程不需要返回任何数据,直接设为NULL即可

Test demo:

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

void print_id(char *s)
{
        printf("%s :pid is %u tid is %ld\n", s, getpid(), pthread_self());
}

void* callback(void *arg)
{
        print_id(arg);
        for (int i = 0; i < 5; i++) {
                printf("子线程: i = %d\n", i);
        }
        printf("子线程: %ld\n", pthread_self());

        return (void *)0;
}

int main()
{
        pthread_t tid;
        pthread_create(&tid, NULL, callback, "new thread");

        printf("pid is %u tid is %ld\n", getpid(), pthread_self());
        printf("主线程: %ld\n", pthread_self());
        pthread_exit(NULL);        

        return 0;
}

Results of the:

pid is 25055 tid is 140031551674112
主线程: 140031551674112
new thread :pid is 25055 tid is 140031543367424
子线程: i = 0
子线程: i = 1
子线程: i = 2
子线程: i = 3
子线程: i = 4
子线程: 140031543367424

Comparing the examples in the third point of creating threads, it can be seen that the pthread_exit function exits the main thread, but does not release the virtual address space resources.

5. Thread recovery function

Threads are the same as processes. When a child thread exits, its kernel resources are mainly recovered by the main thread. The thread recycling function provided in the thread library is pthread_join(). This function is a blocking function. If there are still child threads running, calling this function will It will block, and the sub-thread exits the function to unblock and recycle resources. The function is called once, and only one sub-thread can be recycled. If there are multiple sub-threads, the recycling operation needs to be performed in a loop.

In addition, through the thread recovery function, the data passed when the child thread exits can also be obtained, as follows:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

参数说明:
thread:等待退出线程的线程号
retval:退出线程的返回值,二级指针,是一个传出函数,这个地址中存储了pthread_exit()传递出的数据,如果不需要,可以为NULL

返回值:线程回收成功返回0,回收失败返回错误号

Test demo1:

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

struct test
{
        int num;
        int age;
};

struct test t;        // 全局变量多线程共享

void print_id(char *s)
{
        printf("%s :pid is %u tid is %ld\n", s, getpid(), pthread_self());
}

void* callback(void *arg)
{
        print_id(arg);
        for (int i = 0; i < 5; i++) {
                printf("子线程: i = %d\n", i);
        }

        // struct test t;    栈区被释放了,所以test不能是局部变量,定义1
        t.num = 100;
        t.age = 50;

        pthread_exit(&t);

        printf("子线程: %ld\n", pthread_self());

        return (void *)0;
}

int main()
{
        pthread_t tid;
        pthread_create(&tid, NULL, callback, "new thread");

        printf("pid is %u tid is %ld\n", getpid(), pthread_self());
        printf("主线程: %ld\n", pthread_self());

        void *ptr;        // ptr指向pthread_exit退出时t的地址
        pthread_join(tid, &ptr);
        struct test *pt = (struct test*)ptr;
        printf("num: %d, age = %d\n", pt->num, pt->age);

        return 0;
}

Results of the:

pid is 26531 tid is 139762155865856
主线程: 139762155865856
new thread :pid is 26531 tid is 139762147559168
子线程: i = 0
子线程: i = 1
子线程: i = 2
子线程: i = 3
子线程: i = 4
num: 100, age = 50

Analysis: test t cannot be defined at 1 place, because test t is the stack memory in the sub-thread. In a virtual memory space, there is only one stack. This stack is divided equally by multiple sub-threads. When a sub-thread exits, the The used stack is released, and what we take out is actually a random number. What if the data is guaranteed to be correct? To ensure that this address is not released, you can use heap memory or a global variable, because multiple threads share the global data area and heap area, as long as multiple threads can access this memory.

Test demo2:

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

struct test
{
        int num;
        int age;
};

// struct test t;        // 全局变量多线程共享

void print_id(char *s)
{
        printf("pid is %u tid is %ld\n", getpid(), pthread_self());
}

void* callback(void *arg)
{
        print_id(arg);
        for (int i = 0; i < 5; i++) {
                printf("子线程: i = %d\n", i);
        }

        struct test*t = (struct test*)arg;
        // struct test t;    栈区被释放了,所以test不能是局部变量
        t->num = 100;
        t->age = 50;

        pthread_exit(t);

        printf("子线程: %ld\n", pthread_self());

        return (void *)0;
}

int main()
{
        struct test t;
        pthread_t tid;
        pthread_create(&tid, NULL, callback, &t);

        printf("pid is %u tid is %ld\n", getpid(), pthread_self());
        printf("主线程: %ld\n", pthread_self());

        void *ptr;        // ptr指向pthread_exit退出时t的地址
        pthread_join(tid, &ptr);
        struct test *pt = (struct test*)ptr;
        printf("num: %d, age = %d\n", pt->num, pt->age);

        return 0;
}

Results of the:

pid is 27333 tid is 140069782001408
主线程: 140069782001408
pid is 27333 tid is 140069773694720
子线程: i = 0
子线程: i = 1
子线程: i = 2
子线程: i = 3
子线程: i = 4
num: 100, age = 50

Analysis: Multiple threads divide the stack space equally, and the corresponding stack space cannot be accessed in multiple threads, but the stack space of the main thread is passed to the sub-thread actively, then it can be accessed, the main thread and the sub-thread are the same Virtual address space, so it can be accessed. When the child thread is released, the stack space of the main thread still exists, and it can be executed normally when called.

6. Thread separation function

Under normal circumstances, the main thread in the program has its own processing flow. If the main thread is responsible for the resource recovery of the sub-thread, calling pthread_join() will be blocked as long as the sub-thread does not exit, so the tasks of the main thread cannot be executed

The thread detachment function pthread_detach() is provided for us in the thread library function. After calling this function, the specified sub-thread can be separated from the main thread. When the sub-thread exits, the kernel resources it occupies will be taken over by other processes in the system. Recycled. After the thread is separated, the use of pthread_join() in the main thread will not reclaim the sub-thread resources.

#include <pthread.h>

int pthread_detach(pthread_t thread);

参数解释:
thread:线程id

Test demo:

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

struct test
{
        int num;
        int age;
};

void print_id(char *s)
{
        printf("pid is %u tid is %ld\n", getpid(), pthread_self());
}

void* callback(void *arg)
{
        print_id(arg);
        for (int i = 0; i < 5; i++) {
                printf("子线程: i = %d\n", i);
        }

        struct test*t = (struct test*)arg;
        t->num = 100;
        t->age = 50;

        pthread_exit(t);

        printf("子线程: %ld\n", pthread_self());

        return (void *)0;
}

int main()
{
        struct test t;
        pthread_t tid;
        pthread_create(&tid, NULL, callback, &t);

        printf("pid is %u tid is %ld\n", getpid(), pthread_self());
        printf("主线程: %ld\n", pthread_self());

        pthread_detach(tid);
        pthread_exit(NULL);

        return 0;
}

Results of the:

pid is 28021 tid is 140366746593024
主线程: 140366746593024
pid is 28021 tid is 140366738286336
子线程: i = 0
子线程: i = 1
子线程: i = 2
子线程: i = 3
子线程: i = 4

Analysis: Use the pthread_detach function to directly separate the sub-thread from the main thread. After the sub-thread function is executed, it exits and is automatically recycled by the kernel function, and the main thread will not be blocked.

7. Thread cancellation function

The thread cancel function means to kill another thread in one thread under certain circumstances. Using this function to kill another thread requires two steps:

1) Call the thread cancellation function pthread_cancel in thread A, and specify to cancel thread B, which means that B cannot be canceled

2) Thread B makes a system call (switch from user mode to kernel mode), otherwise thread B can run forever

#include <pthread.h>

int pthread_cancel(pthread_t pid);

参数:线程号

返回值:成功返回0,失败返回错误码

The second point is to execute printf to print, and printf will eventually write to the terminal, so the read method will be called at the bottom layer.

8. Thread ID comparison function

In Linux, the thread ID is essentially an unsigned long integer, so you can directly use the comparison operator to compare the IDs of two threads:

#include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);

返回值:ID相等返回值不等于0,不相等返回值等于0

Guess you like

Origin blog.csdn.net/qq_58550520/article/details/129070773