• [Linux]多线程编程


    [Linux]多线程编程

    Linux操作系统下,并没有真正意义上的线程,而是由进程中的轻量级进程(LWP)模拟的线程,因此Linux操作系统中只会提供进程操作的系统接口。但是为了用户操作方便,Linux操作系统提供了用户级的原生线程库,原生线程库将系统接口进行封装,让用户可以像使用操作真正的线程一样进行线程操作,另外由于使用的是原生线程库,编译代码时需要指明线程库进行链接。

    pthread_create函数

    pthread_create函数用于创建线程。

    //pthread_create函数所在的头文件和函数声明
    #include 
    
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
    
    • 1
    • 2
    • 3
    • 4
    • **thread参数:**返回线程ID

    • **attr参数:**设置线程的属性,attr为NULL表示使用默认属性

    • start_routine参数:是个函数地址,线程启动后要执行的函数

    • **arg参数:**传给线程启动函数的参数

    • **返回值:**成功返回0,失败返回错误码。(由于线程共用同一个地址空间,因此不采用设置全局变量errno的方式记录错误码)

    编写如下代码进行测试:

    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    编译代码并运行查看结果:

    pthreadTest1

    另外,可以使用Linux操作系统的指令ps -aL | head -1 && ps -aL | grep 进程名查看线程:

    pthreadTest2

    其中LWPid和pid相同的是主线程,其余的是新线程。

    pthread_join函数

    和进程类似,Linux操作系统下线程退出后,也要进行线程等待回收新线程,因此提供了pthread_join函数。

    //pthread_join函数所在的头文件和函数声明
     #include 
    
    int pthread_join(pthread_t thread, void **retval);
    
    • 1
    • 2
    • 3
    • 4
    • thread参数: 要等待并回收的新线程id。(pthread_create函数创建新线程时的输出型参数thread)
    • retval参数: 作为输出型参数接收线程退出的返回值。
    • **返回值:**成功返回0,失败返回错误码。
    • 如果线程被取消了,retval参数会接收到PTHREAD_CANCELED ((void *) -1)

    注意: 由于线程异常会产生信号直接导致进程终止,因此线程等待回收时不需要考虑异常情况检测。

    编写如下代码进行测试:

    #include 
    #include 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    编译代码并运行查看结果,在进程执行时采用指令while :; do ps -aL | head -1 && ps -aL | grep 进程名; sleep 1; done检测进程:

    pthreadTest3

    可以看到线程退出return返回了返回值为0的数据,主线程调用pthread_join能够成功接收返回值。

    pthread_exit函数

    Linux操作系统下线程退出的方式:

    1. 线程执行函数结束,return返回
    2. 调用phread_exit函数退出线程

    注意: 无论是线程执行函数结束,return返回还是调用phread_exit函数退出线程,最终都会给主线程返回一个void *类型的返回值。

    //pthread_exit函数所在的头文件和函数声明
    #include 
    
    void pthread_exit(void *retval);
    
    • 1
    • 2
    • 3
    • 4
    • retval参数: 作为线程终止的返回值返回给主线程。

    编写如下代码进行测试:

    #include 
    #include 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    编译代码并运行查看结果:

    pthreadTest4

    可以看到线程退出phread_exit返回了值为1的数据,主线程调用pthread_join能够成功接收返回值。

    pthread_cancel函数

    pthread_cancel函数能够将正在运行的线程取消。

    //pthread_cancel函数所在的头文件和函数声明
    #include 
    
    int pthread_cancel(pthread_t thread);
    
    • 1
    • 2
    • 3
    • 4
    • thread参数: 要取消的线程id。
    • **返回值:**成功返回0,失败返回错误码。

    编写如下代码进行测试:

    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    编译代码并运行查看结果:

    pthreadTest5

    死循环的新线程被主线程取消了,新线程被取消后,主线程pthread_join函数接收到的是PTHREAD_CANCELED ((void *) -1)

    pthread_self函数

    pthread_self函数用于获取当前线程的线程id。

    //pthread_self函数所在的头文件和函数声明
    #include 
    
    pthread_t pthread_self(void);
    
    • 1
    • 2
    • 3
    • 4
    • 返回值: 返回调用线程的线程id。

    编写如下代码进行测试:

    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    编译代码并运行查看结果:

    image-20230923174750463

    pthread_detach函数

    默认情况下,新创建的线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。如果我们将线程分离,当线程退出时,自动释放线程资源。Linux操作系统下提供了pthread_detach函数用于分离线程。

    //pthread_detach函数所在的头文件和函数声明
    #include 
    
    int pthread_detach(pthread_t thread);
    
    • 1
    • 2
    • 3
    • 4
    • thread参数: 要分离的线程id。
    • 线程分离后无法进行pthread_join操作,如果使用了就会报错。

    先编写如下代码进行测试:

    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    编译代码并运行查看结果:

    image-20230923193701650

    由于主线程和新线程的调度问题,造成了如上两种情况,但是无论哪种情况,新线程分离后,在进行等待操作就会报错。

    再编写如下代码进行测试:

    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    编译代码并运行查看结果:

    pthreadTest6

    主线程先进行等待,新线程后进行分离,就会造成如上这种即使线程分离了等待操作也未报错。因为在主线程进行等待操作时检测新线程未分离,就直接进入阻塞等待新线程的状态了,因此不会报错。

    理解线程库和线程id

    Linux操作系统下,没有真正的线程,而是用轻量级进程模拟的线程,因此Linux操作系统只能以轻量级进程的方式进行管理,而不能以线程的方式管理,而线程库要给用户提供线程相关的各种各样的操作,线程库就要承担一部分操作系统不具有的线程管理操作,因此用于调用线程库创建线程时,线程库就要创建对应的数据结构记录线程的属性以用于管理线程,在后续调用线程库操作线程时,线程库就会利用之前创建的记录属性的结构和封装的一些系统接口来实现线程操作。

    image-20230923203756664

    线程库在组织线程管理结构时,会将其线性的记录在进程地址空间上,而线程管理结构在进程地址空间上的首地址就是线程库提供的线程id。

    image-20230923204156954

    在线程库提供的线程管理结构中,存在一部分空间称为线程栈,线程栈是每个新线程私有的栈,新线程会将创建的临时变量存在线程栈中,将每个线程的数据分离开,以便进行数据的管理,而主线程使用的是进程地址空间中的栈结构。

    说明: 在Linux操作系统下,C++提供的线程操作都是对原生线程库的封装。

    编写如下代码进行测试:

    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在编译时不加-lpthread选项并运行程序结果如下:

    image-20230923205821115

    由于C++的线程操作是封装原生线程库得来的,如果编译时不链接原生线程库,在程序执行时,操作系统会无法正确的加载动态库到内存中,会导致程序无法正常执行。

    补充: 线程管理结构中线程局部存储用于存储线程相关的全局变量、线程上下文信息、隔离敏感数据。

    image-20230927102412819

    编写如下代码进行测试:

    #include 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    编译代码并运行查看结果:

    image-20230927102959242

  • 相关阅读:
    聊聊logback的EvaluatorFilter
    【Mysql】Innodb数据结构(四)
    数列计算
    java计算机毕业设计西藏民族大学论文管理系统源程序+mysql+系统+lw文档+远程调试
    文献越读_细菌中5‘UTR上RG4促进翻译效率
    YCSB and TPC-C on MySQL(避免重复load)
    canvas
    CSDN21天学习挑战赛 - 第三篇打卡文章
    周赛368(模拟、前后缀分解、枚举+数学、预处理+划分型DP)
    江协科技STM32学习- 2安装Keil5-MDK
  • 原文地址:https://blog.csdn.net/csdn_myhome/article/details/133343059