Linux线程——对线程库简单的面向对象封装

发布于:2025-08-13 ⋅ 阅读:(27) ⋅ 点赞:(0)

对线程库简单的面向对象封装

其实这个工作c++11已经做好了。但是,为了能够更清晰地理解线程控制接口的相关使用,同时理解c++线程库的基本使用,在这里,我们尝试着对Linux的原生线程库pthread进行简单的封装!


注意:
这里我们只是为了更深地理解线程库地使用。以及锻炼面向对象封装的过程。
但是,在线程部分,我们还是又很多的内容没有讲到的,如线程同步、互斥、共享数据保护…

本篇文章只是基于当前已有认知上,简单地对pthread进行封装!故有些地方是有问题的!

常规版本——基础使用

首先,不管那么多,先简单地对代码封装进行实现,并且使用一下。然后将在基础版本的代码之上,进行一些扩展使用!

线程库面向对象的思考过程

我们翻开c++文档,我们可以发现,线程的很多操作都被封装成了面向对象的接口!即抽象化出要调用的方法,底层的实现是被藏起来的!我们的目标就是如此。


首先,我们需要对线程面向对象的使用有一个基本的认知:

1.我们希望的是,能够通过创建一个Thread对象就能成功创建一个线程(完成一些线程基本属性创建)
2.通过接口Start来启动线程,即让线程开始执行代码
3.通过接口Detach来让线程分离
4.通过接口Stop终止线程
5.通过接口Join来回收线程

需要说明的是:
上述的所有接口,都是站在主线程的角度来看待的。所以也就是说,上述的接口都是主线程操作创建的线程!

线程类代码前置准备

前置准备其实也就是搭建线程类基本框架:
如线程类的成员变量,构造,有哪些接口,引入相关头文件:

#pragma once

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



namespace myThread{
    // 直接用包装器包装一个调用函数 这个函数就是未来创建线程执行的任务!
    using func_t = std::function<void()>;

    static uint32_t thread_id = 1;

    class Thread{
    public:    
        Thread(func_t func)
            :_tid(0),
            _func(func),
            _is_detach(false),
            _is_running(false),
            _result(nullptr)
        {
            char* name = new char[64]{0};
            snprintf(name, 64, "thread_%d", thread_id++);
            _name = name;
            delete[] name;
        }

        // 析构函数什么也不用写
        ~Thread(){}

        // 启动线程
        bool Start(){}
     
        // 分离线程
        void Detach(){}
           
        // 终止线程
        void Stop(){}
         

        // 回收线程
        void Join(){}
        
    private:
        pthread_t _tid;    // 线程的id
        func_t _func;      // 线程未来执行的任务
        bool _is_detach;   // 判断线程是否需要分离
        bool _is_running;  // 判断线程是否处于运行状态
        void* _result;     // 线程结束后的返回值
        std::string _name; // 线程名字
    };
}

上述就是基础框架。我们这里需要说明:
c++线程库在使用的时候,创建对象的时候就需要我们手动的把创建线程的执行任务传入。所以,这里我的实现中,用包装器function包装一个void (void)类型的函数作为线程执行的任务函数!
(不过,线程最终执行函数的时候,是(void*) (void*),这个不怕,到时候进行一层封装即可,即把真正要调用的函数放在类内,让类内函数回调外界传入的函数)。


然后还通过一个计数器来给线程命名。不过这里我们这样写是会出现问题的,需要我们通过sleep来控制每个线程创建的时间间隔,否则可能会影响每个线程拿到的名字!


我们还需要设置线程的相关状态:如线程是否分离、线程是否运行。
因为很多时候,需要根据这两个状态来判断当前操作是否合法:

1.如线程已经运行起来了,那就不能重复Start!
2.线程运行起来的设置分离和未运行的设置分离是有区别的。前者需要手动的在系统层面设置分离,后者只需要设置状态即可。
3.Join内部需要判断是否有分离。如果有就不进行回收。

在成员变量中,还添加了一个返回值void* res,这个其实是用来接收返回值的。不过基础版本下,我们并不关心返回值,所以这里统一设为nullptr

基础版本源码

这里就直接上源代码和使用代码了,都有注释,就不解释了:

Thread.hpp

#pragma once

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



namespace myThread{
    // 直接用包装器包装一个调用函数 这个函数就是未来创建线程执行的任务!
    using func_t = std::function<void()>;

    static uint32_t thread_id = 1;

    class Thread{
    private:

        // 通过Linux下特有的函数,将线程名字设置到 “线程局部存储”
        void SetThreadName(){
            int setname_id = pthread_setname_np(_tid, _name.c_str());
            if(setname_id == 0) {std::cout << "线程名字" << _name << "被设置到系统中" << std::endl;}
            else {std::cerr << "线程名字" << _name << "被设置到系统失败" << std::endl;}
        }

        void SetDetach(){
            std::cout << "线程" << _name << "分离" << std::endl;
            _is_detach = true;
        }

        void SetRunning(){_is_running = true;}
        void DisRunning() {_is_running = false;}


        // 不加static是有问题的,不能传给pthread_create的函数指针 -> 因为类内函数默认有this指针(两个参数)
        static void* Routine(void* args){
            Thread* self = static_cast<Thread*>(args);
            // 调用函数

            // 这里就先不管如何通过函数把返回值带出来,等到实现一份带有模板参数的就可以了
            self->_func();
            // 这个函数到这里结束了,马上设置运行状态
            self->DisRunning();
            return nullptr;
        }
        // 但是,加了static,没有this指针,没办法用类内的变量和函数! 所以,调用这个函数的时候传。
        // 所以,这就是为什么pthread_create最后一个参数传的是this指针


    public:    
        Thread(func_t func)
            :_tid(0),
            _func(func),
            _is_detach(false),
            _is_running(false),
            _result(nullptr)
        {
            char* name = new char[64]{0};
            snprintf(name, 64, "thread_%d", thread_id++);
            _name = name;
            delete[] name;
        }

        // 析构函数什么也不用写
        ~Thread(){}

        // 启动线程
        bool Start(){
            // 如果线程已经启动,那就直接返回 -> 不能让线程重复启动
            if(_is_running){
                std::cout << "线程" << _name << "已经被创建!" << std::endl;
                return false;
            }

            // 这个Routine函数是从外部传来的void(),让类内部的void*(void*)函数进行回调
            else{
                int createThread_id = pthread_create(&_tid, nullptr, Routine, this);
                if(createThread_id == 0) {
                    std::cout << "线程" << _name << "创建成功" << std::endl;
                    SetThreadName(); // 把名字设置到系统内
                    SetRunning(); // 将该进程设置为运行状态 

                    // 为了防止出现一种情况:即在线程Start之前就Detach
                    // 如果这里不这么做,会导致系统层面并没有真正的Detach该线程
                    if(_is_detach) SetDetach();
                    return true;
                    return true;
                }
                else{
                    std::cerr << "线程" << _name << "创建失败" << std::endl;
                    return false;
                }
            }
        }

        // 分离线程
        void Detach(){
            // 已经设置了分离了,那就不操作了
            if(_is_detach) return;

            if(_is_running){
                // 如果当前线程已经跑起来了,那么就需要手动的调用接口来设置分离
                pthread_detach(_tid);
            }
            SetDetach();
        }


        // 终止线程
        void Stop(){
            // 只有线程处于运行状态才要终止
            if(_is_running){
                int cancelThread_id = pthread_cancel(_tid);
                if(cancelThread_id == 0) {
                    std::cout << "线程" << _name << "终止" << std::endl;
                    DisRunning();
                }
                else {std::cout << "线程" << _name << "终止失败" << std::endl;}
            }
        }

        // 回收线程
        void Join(){
            if(_is_detach){
                std::cerr << "线程" << _name << ", tid[" << _tid << "]" << "被分离, 无法回收" << std::endl;
            }
            else{
                // 线程的回收不需要判断线程是否还在运行
                int joinThread_id = pthread_join(_tid, nullptr); // 先不关心返回值的问题
                if(joinThread_id == 0){
                    std::cerr << "线程" << _name << "回收成功" << std::endl;
                }
            }
        }

        

    private:
        pthread_t _tid;    // 线程的id
        func_t _func;      // 线程未来执行的任务
        bool _is_detach;   // 判断线程是否需要分离
        bool _is_running;  // 判断线程是否处于运行状态
        void* _result;     // 线程结束后的返回值
        std::string _name; // 线程名字
    };





}

Main.cpp

#include "Thread.hpp"

using namespace myThread;
#include <vector>

int main(){
    auto l1 = []{
        //int cnt = 10;
        while(1){
            std::cout << "i am a thread" << std::endl;
            sleep(1);
        }
    };

    std::vector<Thread> _threads;

    for(int i = 0; i < 5; ++i){
        Thread thread(l1);
        _threads.push_back(thread);
    }

    for(int i = 0; i < 5; ++i){
        _threads[i].Start();
    }

    int cnt = 10;
    while(cnt--){
        std::cout << "main thread..." << std::endl;
        sleep(1);
    }
    
    for(int i = 0; i < 5; ++i){
        _threads[i].Stop();
        _threads[i].Join();
    }

    
    return 0;
}

注: 使用代码均采用创建多个现成的办法来进行测试,且没有对共享资源做数据保护(如显示器文件),所以打印出来的结果可能是混乱的!
在这里插入图片描述
在这里插入图片描述

进阶版本——带有模板参数的使用

用户传参的需求

这里会有一种需求:
就是希望在用户层中,给Thread类传递若干个参数。这些参数就是给线程执行的函数来调用。


在c++线程库下,采用的是可变模板参数实现!即用户可以传递若干个参数。但是,使用模板参数是比较麻烦的,需要考虑到很多c++的新特性。

所以,在这里为了简单模拟实现这个功能,我们采用模板参数!如果想要传递多个参数,就把这些参数都放在一个类下。

进阶源码

#pragma once

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



namespace myThread{
    // 直接用包装器包装一个调用函数 这个函数就是未来创建线程执行的任务!

    static uint32_t thread_id = 1;

    template<class T>
    class Thread{
    private:

        using func_t = std::function<void(T)>;
        

        // 通过Linux下特有的函数,将线程名字设置到 “线程局部存储”
        void SetThreadName(){
            int setname_id = pthread_setname_np(_tid, _name.c_str());
            if(setname_id == 0) {std::cout << "线程名字" << _name << "被设置到系统中" << std::endl;}
            else {std::cerr << "线程名字" << _name << "被设置到系统失败" << std::endl;}
        }

        void SetDetach(){
            std::cout << "线程" << _name << "分离" << std::endl;
            _is_detach = true;
        }

        void SetRunning(){_is_running = true;}
        void DisRunning() {_is_running = false;}


        // 不加static是有问题的,不能传给pthread_create的函数指针 -> 因为类内函数默认有this指针(两个参数)
        static void* Routine(void* args){
            Thread* self = static_cast<Thread*>(args);
            // 调用函数



            // 这里就先不管如何通过函数把返回值带出来,等到实现一份带有模板参数的就可以了
            self->_func(self->_data);
            // 这个函数到这里结束了,马上设置运行状态
            self->DisRunning();
            return nullptr;
        }
        // 但是,加了static,没有this指针,没办法用类内的变量和函数! 所以,调用这个函数的时候传。
        // 所以,这就是为什么pthread_create最后一个参数传的是this指针


    public:    
        Thread(func_t func, T data)
            :_tid(0),
            _func(func),
            _is_detach(false),
            _is_running(false),
            _result(nullptr),
            _data(data)
        {
            char* name = new char[64]{0};
            snprintf(name, 64, "thread_%d", thread_id++);
            _name = name;
            delete[] name;
        }

        // 析构函数什么也不用写
        ~Thread(){}

        // 启动线程
        bool Start(){
            // 如果线程已经启动,那就直接返回 -> 不能让线程重复启动
            if(_is_running){
                std::cout << "线程" << _name << "已经被创建!" << std::endl;
                return false;
            }

            // 这个Routine函数是从外部传来的void(),让类内部的void*(void*)函数进行回调
            else{
                int createThread_id = pthread_create(&_tid, nullptr, Routine, this);
                if(createThread_id == 0) {
                    std::cout << "线程" << _name << "创建成功" << std::endl;
                    SetThreadName(); // 把名字设置到系统内
                    SetRunning(); // 将该进程设置为运行状态 

                    // 为了防止出现一种情况:即在线程Start之前就Detach
                    // 如果这里不这么做,会导致系统层面并没有真正的Detach该线程
                    if(_is_detach) SetDetach();
                    return true;
                }
                else{
                    std::cerr << "线程" << _name << "创建失败" << std::endl;
                    return false;
                }
            }
        }

        // 分离线程
        void Detach(){
            // 已经设置了分离了,那就不操作了
            if(_is_detach) return;

            if(_is_running){
                // 如果当前线程已经跑起来了,那么就需要手动的调用接口来设置分离
                pthread_detach(_tid);
            }
            SetDetach();
        }


        // 终止线程
        void Stop(){
            // 只有线程处于运行状态才要终止
            if(_is_running){
                int cancelThread_id = pthread_cancel(_tid);
                if(cancelThread_id == 0) {
                    std::cout << "线程" << _name << "终止" << std::endl;
                    DisRunning();
                }
                else {std::cout << "线程" << _name << "终止失败" << std::endl;}
            }
        }

        // 回收线程
        void Join(){
            if(_is_detach){
                std::cerr << "线程" << _name << ", tid[" << _tid << "]" << "被分离, 无法回收" << std::endl;
            }
            else{
                // 线程的回收不需要判断线程是否还在运行
                int joinThread_id = pthread_join(_tid, nullptr); // 先不关心返回值的问题
                if(joinThread_id == 0){
                    std::cerr << "线程" << _name << "回收成功" << std::endl;
                }
            }
        }


        // 获取到当前线程的id
        pthread_t GetTid() const{
            return _tid;
        }
        
        // 获取当前线程名字
        std::string GetName(){
            return _name;
        }

    private:
        pthread_t _tid;    // 线程的id
        func_t _func;      // 线程未来执行的任务
        bool _is_detach;   // 判断线程是否需要分离
        bool _is_running;  // 判断线程是否处于运行状态
        void* _result;     // 线程结束后的返回值
        std::string _name; // 线程名字
        T _data;           // 线程执行函数的时候用到的参数
    };

}

其实就是在Thread类内,用到了外界传参的地方,增加一个模板参数!

进阶使用

Main.cpp

#include "Thread.hpp"

using namespace myThread;
#include <vector>

// 放在main函数中不好操作


class ThreadTask{
public:

    ThreadTask(int taski, int cnt = 10)
        :_taski(taski),
        _cnt(cnt),
        _exec_res(0),
        _tid(0),
        _name("")
    {}

    ~ThreadTask(){}

    void Execute(){
        _exec_res = _cnt + 5;
    }


    int _taski; 
    int _cnt;
    int _exec_res;
    pthread_t _tid;
    std::string _name;
};

std::vector<Thread<ThreadTask>> _threads;
std::vector<ThreadTask> _threadtask;

void Routine(ThreadTask data){
    // 沉睡一下,保证线程启动的时候,数据已经设置到thread_task数组了
    sleep(1);

    data._name = _threadtask[data._taski]._name;
    data._tid = _threadtask[data._taski]._tid;

    int cnt = data._cnt;
    while(cnt--){
        std::cout << data._name << "thread, tid is " << data._tid << std::endl;
        sleep(1); 
    }

    std::cout << data._name << " : " << std::endl;
    std::cout << "执行Execute任务" << std::endl; 
    data.Execute();
    std::cout << "执行任务结果" << data._exec_res << std::endl;
}

int main(){

    //std::vector<Thread<ThreadTask>> _threads;
    //std::vector<ThreadTask> _threadtask;

    // 创建一批线程
    for(int i = 0; i < 10; ++i){
        ThreadTask thread_task(i, 5);
        _threadtask.push_back(thread_task);

        Thread<ThreadTask> thread(Routine, thread_task);
        _threads.push_back(thread);


        // 错误写法!!! 这里push_back调用了移动构造(Thread内没写拷贝 / 移动构造)
        // 移动构造本质是掠夺资源 -> 一旦访问被移动的有资源的类型(function...) -> 线程崩溃!
        // 所以,如果写下面两句代码
        // 第一个线程跑起来一会儿,就访问到了 thread_task._tid = thread.GetTid();
        // 访问到了被移动的资源 -> 直接崩
        /* thread_task._name = thread.GetName();
        thread_task._tid = thread.GetTid(); */


        // 这样写不用怕报错 -> 因为资源被略到数组上了
        /* thread_task._name = _threads[i].GetName();
        thread_task._tid = _threads[i].GetTid(); */
        // 但是,这样子即使线程跑起来了,也没办法让Routine执行的时候拿到这里thread_task修改的数据
        // 因为,Thread<ThreadTask> thread(Routine, thread_task);创建的时候
        // 底层保存的T _data 其实是thread_task的拷贝
        // 我们在主线程中对thread_task修改,其他线程中保存的都是拷贝,所以是没办法给到那里的!
    }

    // 启动线程
    for(int i = 0; i < _threads.size(); ++i){
        _threads[i].Start();
        _threadtask[i]._name = _threads[i].GetName();
        _threadtask[i]._tid = _threads[i].GetTid();
    } 

    /* for(int i = 0; i < _threads.size(); ++i){
        _threads[i].Detach();
    } */

    for(int i = 0; i < _threads.size(); ++i){
        _threads[i].Join();
    }

    return 0;
}

使用结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里能够简单验证使用结果即可。
还是因为没有对共享资源保护的原因,会导致出现一些奇怪现象!