[Linux]Multi-threaded programming

[Linux]Multi-threaded programming

Under the Linux operating system, there are no real threads, but threads simulated by lightweight processes (LWP) in the process. Therefore, the Linux operating system only provides system interfaces for process operations. However, for the convenience of user operation, the Linux operating system provides a user-level native thread library. The native thread library encapsulates the system interface so that users can perform thread operations as if they were real threads. In addition, because the native thread library is used, When compiling the code, you need to specify the thread library for linking.

pthread_create function

pthread_createFunction is used to create threads.

//pthread_create函数所在的头文件和函数声明
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • **thread parameter:** Return thread ID

  • **attr parameter: **Set the attributes of the thread. Attr is NULL to use the default attributes.

  • start_routine parameter : It is a function address, the function to be executed after the thread is started.

  • **arg parameter:**The parameter passed to the thread startup function

  • **Return value:** Returns 0 if successful, and error code if failed. ( Since threads share the same address space, the error code is not recorded by setting the global variable errno )

Write the following code for testing:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

using namespace std;

void *thread_run(void *args)
{
    
    
    while(true)
    {
    
    
        cout << "new pthread running" << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    
    
    pthread_t t;
    int n = pthread_create(&t, nullptr, thread_run, nullptr);
    if (n!=0) cerr << "new thread error" << endl; 

    while(true)
    {
    
    
        cout << "main pthread running, new pthread id: " << t << endl;
        sleep(1);
    }
    return 0;
}

Compile the code and run to see the results:

pthreadTest1

In addition, you can use the Linux operating system's instructions ps -aL | head -1 && ps -aL | grep 进程名to view threads:

pthreadTest2

The one with the same LWPid and pid is the main thread, and the rest are new threads.

pthread_join function

Similar to the process, after the thread exits under the Linux operating system, the thread must also wait for the new thread to be recycled, so a function is provided pthread_join.

//pthread_join函数所在的头文件和函数声明
 #include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
  • Thread parameter: The new thread id to wait for and recycle. ( pthread_createThe output parameter thread when the function creates a new thread)
  • retval parameter: Receive the return value of thread exit as an output parameter.
  • **Return value:** Returns 0 if successful, and error code if failed.
  • If the thread is canceled, the retval parameter will be received PTHREAD_CANCELED ((void *) -1).

Note: Since thread exceptions will generate signals that directly cause the process to terminate, there is no need to consider exception detection when the thread is waiting for recycling.

Write the following code for testing:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

#define NUM 10

using namespace std;

void *thread_run(void *args)
{
    
    
    while(true)
    {
    
    
        cout << "new pthread running" << endl;
        sleep(4);
        break;
    }
    return (void*)0;//返回值为0的数据
}

int main()
{
    
    
    pthread_t tid[NUM];
    for (int i = 0; i < NUM; i++)
    {
    
    
        int n = pthread_create(tid+i, nullptr, thread_run, nullptr);
        if (n!=0) cerr << "new thread error, thread-" << i << endl; 
    }

    void *ret = nullptr;
    for (int i = 0; i < NUM; i++)
    {
    
    
        int m = pthread_join(tid[i], &ret);//等待回收新线程
        if (m!=0) cerr << "new thread join error, thread-" << i << endl; 
        cout << "thread-" << i << "quit, ret: " << (uint64_t)ret << endl;//打印新线程退出返回值
    }

    cout << "all thread quit" << endl;//打印说明所有线程退出

    return 0;
}

Compile the code and run it to view the results. Use instructions to detect the process when it is executed while :; do ps -aL | head -1 && ps -aL | grep 进程名; sleep 1; done:

pthreadTest3

You can see that the thread exits and returns data with a return value of 0, and the main thread call pthread_joincan successfully receive the return value.

pthread_exit function

How threads exit under the Linux operating system:

  1. The thread execution function ends and return returns
  2. Call phread_exitfunction to exit thread

Note: Regardless of whether the thread executes the function, returns or calls phread_exitthe function to exit the thread, a void *type of return value will eventually be returned to the main thread.

//pthread_exit函数所在的头文件和函数声明
#include <pthread.h>

void pthread_exit(void *retval);
  • retval parameter: returned to the main thread as the return value of thread termination.

Write the following code for testing:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

#define NUM 10

using namespace std;

void *thread_run(void *args)
{
    
    
    while(true)
    {
    
    
        cout << "new pthread running" << endl;
        sleep(4);
        pthread_exit((void*)1);
    }
}

int main()
{
    
    
    pthread_t tid[NUM];
    for (int i = 0; i < NUM; i++)
    {
    
    
        int n = pthread_create(tid+i, nullptr, thread_run, nullptr);
        if (n!=0) cerr << "new thread error, thread-" << i << endl; 
    }

    void *ret = nullptr;
    for (int i = 0; i < NUM; i++)
    {
    
    
        int m = pthread_join(tid[i], &ret);//等待回收新线程
        if (m!=0) cerr << "new thread join error, thread-" << i << endl; 
        cout << "thread-" << i << "quit, ret: " << (uint64_t)ret << endl;//打印新线程退出返回值
    }

    cout << "all thread quit" << endl;//打印说明所有线程退出

    return 0;
}

Compile the code and run to see the results:

pthreadTest4

You can see that the thread exits phread_exitand returns data with a value of 1, and the main thread call pthread_joincan successfully receive the return value.

pthread_cancel function

pthread_cancelThe function can cancel the running thread.

//pthread_cancel函数所在的头文件和函数声明
#include <pthread.h>

int pthread_cancel(pthread_t thread);
  • thread parameter: the thread id to be canceled.
  • **Return value:** Returns 0 if successful, and error code if failed.

Write the following code for testing:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

using namespace std;

void *thread_run(void *args)
{
    
    
    while (true)//新线程死循环执行代码
    {
    
    
        cout << "new thread running" << endl;
        sleep(1);
    }
    pthread_exit((void *)11);
}

int main()
{
    
    
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, nullptr);

    int cnt = 3;
    while (true)
    {
    
    
        sleep(1);
        if ((cnt--) == 0)
        {
    
    
            pthread_cancel(t);//取消新线程
            break;
        }
    }
    sleep(2);
    void *ret = nullptr;
    pthread_join(t, &ret);//等待回收新线程
    cout << "new thread quit " << "ret: " << (int64_t)ret << endl;
    return 0;
}

Compile the code and run to see the results:

pthreadTest5

The new thread in the infinite loop was canceled by the main thread. After the new thread was canceled, the main thread pthread_joinfunction received PTHREAD_CANCELED ((void *) -1).

pthread_self function

pthread_selfThe function is used to obtain the thread id of the current thread.

//pthread_self函数所在的头文件和函数声明
#include <pthread.h>

pthread_t pthread_self(void);
  • Return value: Returns the thread id of the calling thread.

Write the following code for testing:

#include <iostream>
#include <pthread.h>

using namespace std;


void *thread_run(void *args)
{
    
    
    pthread_t tid = pthread_self();
    cout << "i am new thread, my thread id: " << tid << endl;
    return nullptr;
}

int main()
{
    
    
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, nullptr);
    pthread_join(t, nullptr);
    cout << "new thread id: " << t << endl;
    return 0;
}

Compile the code and run to see the results:

image-20230923174750463

pthread_detach function

By default, after the newly created thread exits, it needs to be operated pthread_join, otherwise the resources cannot be released, causing system leaks. If we separate the thread, the thread resources will be automatically released when the thread exits. The Linux operating system provides pthread_detachfunctions for separating threads.

//pthread_detach函数所在的头文件和函数声明
#include <pthread.h>

int pthread_detach(pthread_t thread);
  • thread parameter: the thread id to be separated.
  • Operations cannot be performed after the thread is separated pthread_join, and an error will be reported if used.

First write the following code for testing:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstring>

using namespace std;

void *thread_run(void *args)
{
    
    
    int cnt = 5;
    while(true)
    {
    
    
        cout << (char*)args << " : " << cnt-- << endl;
        if (cnt==0) break;
    }
    return nullptr;
}

int main()
{
    
    
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, (void*)"new_thread");
    pthread_detach(t);//分离线程

    int n = pthread_join(t, nullptr);//线程等待
    if (n != 0)
    {
    
    
        cerr << "pthread_join error: " << n << " : " << strerror(n) << endl; 
    }
    return 0;
}

Compile the code and run to see the results:

image-20230923193701650

Due to the scheduling problem of the main thread and the new thread, the above two situations are caused. However, in either case, after the new thread is separated, an error will be reported during the waiting operation.

Then write the following code for testing:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstring>

using namespace std;

void *thread_run(void *args)
{
    
    
    pthread_detach(pthread_self());//线程分离
    int cnt = 5;
    while(true)
    {
    
    
        cout << (char*)args << " : " << cnt-- << endl;
        sleep(1);
        if (cnt==0) break;
    }
    return nullptr;
}

int main()
{
    
    
    pthread_t t;
    pthread_create(&t, nullptr, thread_run, (void*)"new_thread");
    int n = pthread_join(t, nullptr);//线程等待
    if (n != 0)
    {
    
    
        cerr << "pthread_join error: " << n << " : " << strerror(n) << endl; 
    }
    return 0;
}

Compile the code and run to see the results:

pthreadTest6

If the main thread waits first and then separates the new thread, it will cause the above situation that even if the thread is separated and the waiting operation does not report an error. Because when the main thread is performing a waiting operation, it is detected that the new thread has not separated, and it directly enters the state of blocking and waiting for the new thread, so no error will be reported.

Understand thread libraries and thread ids

Under the Linux operating system, there are no real threads, but threads simulated by lightweight processes. Therefore, the Linux operating system can only be managed in the form of lightweight processes, not threads, and the thread library must provide Users provide various thread-related operations, and the thread library must undertake some thread management operations that the operating system does not have. Therefore, when the thread library is used to create a thread, the thread library must create a corresponding data structure to record the attributes of the thread. It is used to manage threads. When the thread library is subsequently called to operate a thread, the thread library will use the previously created record attribute structure and some encapsulated system interfaces to implement thread operations.

image-20230923203756664

When the thread library organizes the thread management structure, it will linearly record it in the process address space, and the first address of the thread management structure in the process address space is the thread id provided by the thread library.

image-20230923204156954

In the thread management structure provided by the thread library, there is a part of space called the thread stack. The thread stack is a private stack for each new thread. The new thread will store the created temporary variables in the thread stack and separate the data of each thread. , for data management, and the main thread uses the stack structure in the process address space.

Note: Under the Linux operating system, the thread operations provided by C++ are encapsulation of the native thread library.

Write the following code for testing:

#include <iostream>
#include <unistd.h>
#include <thread>

using namespace std;

void run1()
{
    
    
    while(true)
    {
    
    
        cout << "thread 1" << endl;
        sleep(1);
    }
}
void run2()
{
    
    
    while(true)
    {
    
    
        cout << "thread 2" << endl;
        sleep(1);
    }
}
void run3()
{
    
    
    while(true)
    {
    
    
        cout << "thread 3" << endl;
        sleep(1);
    }
}


int main()
{
    
    
    thread th1(run1);
    thread th2(run2);
    thread th3(run3);

    th1.join();
    th2.join();
    th3.join();

    return 0;
}

When compiling without -lpthreadoptions and running the program, the results are as follows:

image-20230923205821115

Since C++'s thread operations are derived from encapsulating the native thread library, if the native thread library is not linked during compilation, the operating system will not be able to correctly load the dynamic library into the memory when the program is executed, causing the program to fail to execute normally.

Supplement: Thread local storage in the thread management structure is used to store thread-related global variables, thread context information, and isolate sensitive data.

image-20230927102412819

Write the following code for testing:

#include <iostream>
#include <unistd.h>
#include <pthread.h>

using namespace std;

__thread int g_val = 100;//__thread将数据以线程全局变量的形式创建

void *thread_run(void *args)
{
    
    
    char* tname = static_cast<char*>(args);
    int cnt = 5;
    while (true)
    {
    
    
        cout << tname << ":" << (u_int64_t)cnt << ",g_val: " << g_val << ",&g_val: " << &g_val << endl;
        if ((cnt--)==0)
            break;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    
    
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_create(&tid1, nullptr, thread_run, (void*)"thread1");
    pthread_create(&tid2, nullptr, thread_run, (void*)"thread2");
    pthread_create(&tid3, nullptr, thread_run, (void*)"thread3");
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);
    return 0;
}

Compile the code and run to see the results:

image-20230927102959242

Guess you like

Origin blog.csdn.net/csdn_myhome/article/details/133343059