[Linux operating system] Basic knowledge and creation of threads--creating multiple sub-threads in a loop

This article mainly introduces the concept and function of threads, the realization of three-level mapping of threads, the method of creating threads (explaining pthread_self and pthread_create functions), creating multiple sub-threads in a loop as an example, and analyzing the sharing of global variables between threads , hope that helps you.

insert image description here


1. The concept of thread and its function

In computer science, a thread is a unit of execution in a process. A process can have multiple threads, and each thread can perform different tasks independently. Thread is an important tool to achieve concurrent execution, which can improve the efficiency and performance of the program.

effect:

  1. Improve the responsiveness of the program : Through multi-threading, multiple tasks can be executed at the same time, the response speed of the program can be improved, and the entire program can be avoided due to the blocking of one task.

  2. Improve the utilization rate of computer resources : Multi-threading can make full use of the multi-core processor of the computer, execute multiple tasks at the same time, and improve the utilization rate of computer resources.

  3. Realize concurrent programming : threads can be used to realize concurrent programming, such as processing multiple client requests at the same time in the server, or responding to user input at the same time in a graphical interface program.

  4. Realize task decomposition and collaboration : By decomposing a complex task into multiple subtasks, and each subtask is executed by a thread, the maintainability and scalability of the program can be improved. Threads can collaborate and communicate through shared memory or message passing.

insert image description here


Tertiary mapping

In the operating system, the operation of threads needs to be realized through three-level mapping. The three-level mapping includes virtual address-to-physical address mapping, physical address-to-main memory mapping, and main memory-to-disk mapping.

  1. Mapping of virtual addresses to physical addresses : Each thread has its own virtual address space, and the virtual address is the address used by the thread at runtime. The operating system uses page tables to map virtual addresses to physical addresses. A page table divides a virtual address into a series of pages and maps these pages to page frames on physical addresses. When a thread accesses a virtual address, the operating system translates the virtual address into a physical address according to the page table.

  2. Mapping of physical address to main memory : The physical address is the actual hardware address, which represents the memory address in the computer. The operating system manages the mapping relationship between physical addresses and main memory to ensure that threads can access the correct main memory address. The operating system assigns physical addresses to different threads so that the threads can store and access data in main memory.

  3. Main memory-to-disk mapping : Main memory is the temporary storage in a computer that stores data and instructions needed by threads to run. But the capacity of the main memory is limited. When the main memory is not enough to store the data needed by all threads, the operating system will store some data in the virtual memory on the disk. In this way, when a thread needs to access data stored on the disk, the operating system will read the data from the disk into main memory, and then the thread can continue to access the data.


Second, thread sharing and non-sharing

shared thread

A shared thread means that multiple threads can access and modify shared resources of the same process. Shared threads are usually used in scenarios where multiple threads need to share data and cooperate.

In shared threads, multiple threads can read and modify the same shared data at the same time. This shared data can be global variables, static variables, objects in heap memory, etc. Shared threads can realize communication and cooperation between threads by reading and writing operations on shared resources.

The advantage of shared thread is that it can easily share data and cooperate, but at the same time, you also need to pay attention to the issue of thread safety. Simultaneous reading and writing of shared resources by multiple threads may lead to data inconsistency and race conditions. Locking and synchronization mechanisms are required to ensure the correctness and consistency of shared resources.

non-shared thread

Non-shared threads mean that each thread has its own independent resources, which cannot be accessed and modified by other threads. Non-shared threads are usually used to perform independent tasks without the need to communicate and share data with other threads.

In non-shared threads, each thread has its own independent stack space, registers and other resources. This means that each thread can perform its own tasks independently without interfering with each other. The advantage of non-shared threads is that it can improve the concurrency and execution efficiency of the program, but it also limits the communication and cooperation between threads.


Third, the method of creating threads

related functions

Let's first get to know two functions about creating threads: pthread_selfpthread_create


1. pthread_self():

pthread_selfThe function of the function is to get the thread ID of the current thread

Function prototype:

pthread_t pthread_self(void);

This function has no parameters.

Return Value:
Returns the thread ID of the calling thread.


2. pthread_create

pthread_createThe role of the function is to create a new thread and add it to the process. Through this function, we can realize the concurrent execution of multiple threads.

Function prototype:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

parameter:

  • thread: Pointer to the thread ID, used to store the ID of the newly created thread.
  • attr: Pointer to the thread attribute, used to set the attribute of the newly created thread. Can be passed in NULL, using the default properties.
  • start_routine: Pointer to the thread function from which the newly created thread will start executing.
  • arg: The parameter passed to the thread function, which can be any type of pointer.

Return value:
When the thread is successfully created, it returns 0; when the thread creation fails, it returns a non-zero error code.

Examples and explanations:

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

void* thread_func(void* arg) {
    
    
    int thread_num = *(int*)arg;
    printf("Thread %d is running\n", thread_num);
    return NULL;
}

int main() {
    
    
    pthread_t thread1, thread2;
    int arg1 = 1, arg2 = 2;

    pthread_create(&thread1, NULL, thread_func, &arg1);
    pthread_create(&thread2, NULL, thread_func, &arg2);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

In the above example, we defined a thread function thread_functhat takes an integer parameter and prints it out. In mainthe function, we create two threads and pass different parameters to them. With pthread_createthe function, we pass the thread function and parameters to the newly created thread. Then, we use pthread_joina function to wait for both threads to finish executing.

pthread_createfunction is used to create a new thread and join it to the process. This function needs to pass in the thread ID pointer, thread attributes, thread functions and parameters. After the thread is successfully created, the new thread will start executing from the specified thread function and can access the parameters passed to the thread function. In the above example, we can see that the two threads have printed the parameters passed to them respectively, indicating that they have successfully received the parameters and processed them accordingly.


In most programming languages, there are usually two ways to create a thread: inheriting the Thread class and implementing the Runnable interface.

Inherit the Thread class

Inheriting Threadclasses is an easy way to create threads. By inheriting Threadthe class, run()methods can be overridden to define the execution logic of the thread.

ThreadHere is an example of creating a thread using an inherited class:

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

// 继承Thread类,重写run()方法
void* thread_func(void* arg) {
    
    
    // 线程的执行逻辑
    printf("Hello from thread!\n");
    return NULL;
}

int main() {
    
    
    // 创建线程实例
    pthread_t thread;

    // 启动线程
    pthread_create(&thread, NULL, thread_func, NULL);

    // 等待线程执行完成
    pthread_join(thread, NULL);

    return 0;
}

In the above example, we Threadhave rewritten run()the method by inheriting the class, and defined the execution logic of the thread in it. Then use pthread_create()the function to create a thread instance, and pthread_join()wait for the thread execution to complete through the function.

Implement the Runnable interface

Implementing Runnablean interface is another common way to create threads. By implementing Runnablethe interface, the execution logic of the thread can be defined in run()the method.

Here is an Runnableexample of creating a thread using the implement interface:

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

// 实现Runnable接口,定义run()方法
void* thread_func(void* arg) {
    
    
    // 线程的执行逻辑
    printf("Hello from thread!\n");
    return NULL;
}

int main() {
    
    
    // 创建线程实例
    pthread_t thread;
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    // 创建线程对象
    pthread_create(&thread, &attr, thread_func, NULL);

    // 等待线程执行完成
    pthread_join(thread, NULL);

    return 0;
}

In the above example, we Runnabledefined run()the method by implementing the interface, and defined the execution logic of the thread in it. Then use pthread_create(the ) function to create a thread instance, and pthread_join()wait for the thread execution to complete through the function.


4. Create multiple sub-threads in a loop

step

  1. Define a thread function that contains the logic that needs to be executed in the child thread. You can use the thread parameter in this function to distinguish different child threads.

  2. In the main function, use pthread_createa function loop to create multiple child threads. Each child thread calls the same thread function.

  3. In pthread_createthe function, the thread function is passed as a thread function pointer, and the corresponding parameters are passed to the thread function. Parameters can be passed using structures or pointers.

  4. In the thread function, execute the corresponding logic according to the passed parameters.

example

The following is a sample code implemented in C language, which demonstrates how to create multiple sub-threads in a loop, and each sub-thread inherits from the same thread function:

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

// 线程函数
void* threadFunc(void* arg) {
    
    
    int thread_num = *(int*)arg;
    printf("Thread %d is running\n", thread_num);
    // 执行线程的逻辑
    // ...
    return NULL;
}

int main() {
    
    
    const int NUM_THREADS = 5;
    pthread_t threads[NUM_THREADS];
    
    for (int i = 0; i < NUM_THREADS; i++) {
    
    
        int* thread_arg = malloc(sizeof(int));
        *thread_arg = i + 1;
        pthread_create(&threads[i], NULL, threadFunc, thread_arg);
    }
    
    for (int i = 0; i < NUM_THREADS; i++) {
    
    
        pthread_join(threads[i], NULL);
    }
    
    return 0;
}

code explanation

In the above example, we defined a thread function threadFunc, which contains the logic that needs to be executed in the child thread. In the main function, we pthread_createcreated 5 child threads using a function loop. Each child thread calls threadFuncthe function and passes the corresponding parameters to the thread function. In threadFuncthe function, we execute the logic of the thread according to the passed parameters.


Sharing global variables between threads

In multithreaded programming, data sharing between threads is a common problem. Data can be shared between threads through shared memory or global variables.

The following is an example of using global variables to share data between threads:

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

// 全局变量
int count = 0;

// 实现Runnable接口,定义run()方法
void* thread_func(void* arg) {
    
    
    // 访问和修改全局变量
    count++;
    return NULL;
    int main() {
    
    
    // 创建线程实例
    pthread_t thread1, thread2;
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    // 创建线程对象
    pthread_create(&thread1, &attr, thread_func, NULL);
    pthread_create(&thread2, &attr, thread_func, NULL);

    // 等待线程执行完成
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 打印全局变量的值
    printf("Count: %d\n", count);

    return 0;
}

In the above example, we use global variables countto share data between threads. In each thread, we increment countthe value. Then print the value in the main thread count. Since countit is a global variable, all threads can access and modify it, so the final printed countvalue may not be the expected result.

insert image description here

Guess you like

Origin blog.csdn.net/Goforyouqp/article/details/132393065