Linux OS:线程封装 | RAII封装锁 | 随机数运算任务封装

Linux OS:线程封装 | RAII封装锁 | 随机数运算任务封装

一、Linux OS:线程封装

1.1 线程私有成员

现在我们手动封装一个线程,其中类成员变量包含:线程id、线程名、线程运行状态(是否允许)、线程数据、创建线程的回调方法!具体如下:

cpp 复制代码
template <class T>
using func_t = std::function<void(T)>; //回调方法重命名

template <class T>
class Thread
{
private:
    pthread_t _tid; // 线程id
    std::string _threadname; // 线程名
    bool _isrunning; // 线程运行状态(是否允许)
    func_t<T> _func; // 创建出的新线程执行该回调函数
    T _data; // 线程数据
};

1.2 线程成员函数

对于线程我们将实现下面几个成员方面:构造函数初始化相关数据(新创建的线程没有启动运行)、启动线程接口、线程等待接口、获取线程名!

1)构造函数初始化相关数据

对于构造函数,我们通过外部用户传入线程名、执行方法、线程数据!

c 复制代码
Thread(const std::string& threadname, func_t<T> func, T data)
        :_tid(0), _threadname(threadname), _isrunning(false)
        	, _func(func), _data(data)
    {}

2)启动线程接口

线程默认是没有运行的,我们通过在启动线程接口调用pthread_create函数来创建线程,通过构造函数获取到的数据,将相关数据传递给pthread_create函数。

c 复制代码
static void *ThreadRoution(void *args)
{
    Thread *ts = static_cast<Thread *>(args);
    ts->_func(ts->_data);
}

void Start()
{
    int n = pthread_create(&_tid, nullptr, ThreadRoution, this);
    if(n == 0)
    {
        _isrunning = true;
    }
}

为什么我们需要这样设计,在创建时将执行函数设计为静态的,并将this指针作为参数传递给线程执行函数?

原因在于编译器会自动传递一个this指针给类成员函数,并且是第一个参数!所以我们将线程执行函数设计为静态的,静态函数会存在this指针。但我们需要在该函数中调用线程对象的执行方法,所以我们将类对象(this)作为参数传给静态函数!之后在对参数强转即可获得线程对象,然后既可以调用相关执行方法了!

3)线程等待

只有对启动的线程进行等待才是有意义的!这也是我们为啥在类中添加表示线程状态的成员变量的原因之一!

c 复制代码
void Join()
{
    if(_isrunning)
    {
        int n = pthread_join(_tid, nullptr);//等待线程,等待成功返回0
        if(n == 0)
            _isrunning = false;
    }
}

4)获取线程名

c 复制代码
const std::string& GetThreadName()
{
    return _threadname;
}

1.3 总体代码

c 复制代码
template <class T>
using func_t = std::function<void(T)>;

template <class T>
class Thread
{
public:
    Thread(const std::string& threadname, func_t<T> func, T data)
        :_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data)
    {}

    static void *ThreadRoution(void *args)
    {
        Thread *ts = static_cast<Thread *>(args);
        ts->_func(ts->_data);
    }

    void Start()
    {
        int n = pthread_create(&_tid, nullptr, ThreadRoution, this);
        if(n == 0)
        {
            _isrunning = true;
        }
    }

    void Join()
    {
        if(_isrunning)
        {
            int n = pthread_join(_tid, nullptr);
            if(n == 0)
                _isrunning = false;
        }
    }
    const std::string& GetThreadName()
    {
        return _threadname;
    }
    ~Thread()
    {}
private:
    pthread_t _tid;
    std::string _threadname;
    bool _isrunning;
    func_t<T> _func;
    T _data;
};

二、RAII封装锁

在实际项目中,一般不建议用户自己持有锁,释放锁。这样做的后果就是:一旦加锁和解锁之间的代码抛异常,将会导致死锁,进而导致内存泄漏!

所以我们一般采用RAII思想。对于临界区加锁时,我们将锁交给对象(LockGuard)来管理。其中LockGuard只存在一个锁成员函数。用户将锁传递给LockGuard对象后,构造函数初始化列表时将锁的对象创建出来,然后在函数体中进行加锁,在析构函数中封装解锁方法!

c 复制代码
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock):_lock(lock)
    {}
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {}

private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock): _mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.Unlock();
    }
private:
    Mutex _mutex;
};

三、随机数运算任务疯转

下面我们疯转一个类:我们传入两个值和运算符后对数据进行处理。但运算过程中,用户传入运算符可能未知,可能出现除0,模0错误...所以除了运算结果外,我们添加一个_code的成员变量,用于判断结构是否可信!

然后我们用一个枚举集合表示结果集合(0表示结果可信,非0对应相应错误)。然后我们不仅可以通过_code成员变量判断结果是否可行,一旦结果不可信还可以获取到出错原因。

这里我们提供了打印任务和打印运算结果接口、仿函数等接口。比较简单,就不多说了!

c 复制代码
const int defaultResult = 0;
enum
{
    ok = 0,
    div_zero,
    mod_zero,
    unknow
};

class Task
{
public:
    Task()
    {}
    Task(int data_x, int data_y, char op)
        : _data_x(data_x), _data_y(data_y), _op(op), _result(ok), _code(defaultResult)
    {
    }

    void Run()
    {
        switch (_op)
        {
        case '+':
            _result = _data_x + _data_y;
            break;
        case '-':
            _result = _data_x - _data_y;
            break;
        case '*':
            _result = _data_x * _data_y;
            break;
        case '/':
        {
            if (_data_y == 0)
                _code = div_zero;
            else
                _result = _data_x / _data_y;
            break;
        }
        case '%':
        {
            if (_data_y == 0)
                _code = mod_zero;
            else
                _result = _data_x % _data_y;
            break;
        }
        default:
            _code = unknow;
        }
    }

    void operator()()
    {
        Run();
    }

    std::string PrintTask()
    {
        std::string s;
        s += std::to_string(_data_x);
        s += _op;
        s += std::to_string(_data_y);
        s += "=?";
        return s;
    }

    std::string PrintResult()
    {
        std::string s;
        s += std::to_string(_data_x);
        s += _op;
        s += std::to_string(_data_y);
        s += "=";
        s += std::to_string(_result);
        s += ", [";
        s += std::to_string(_code);
        s += "]";
        return s;
    }

    ~Task()
    {
    }

private:
    int _data_x;
    int _data_y;
    char _op;

    int _result;
    int _code; // 错误码为0,结果可行,否则不可信
};
相关推荐
Starry_hello world4 小时前
Linux 的准备工作
linux·笔记·有问必答
Rverdoser4 小时前
服务器(一种管理计算资源的计算机)
运维·服务器
流浪法师125 小时前
SecProxy - 自动化安全协同平台
运维·安全·自动化
_考不上研究生不改名5 小时前
【完美解决】VSCode连接HPC节点,已配置密钥却还是提示需要输入密码
linux·服务器·vscode·远程连接·hpc·超算集群
_长银6 小时前
Vim搜索和替换
linux·编辑器·vim
刚入门的大一新生6 小时前
C++初阶-C++入门基础
开发语言·c++
IT _oA6 小时前
Active Directory 域服务
运维·服务器·网络·windows·笔记
weixin_428498497 小时前
Visual Studio 中使用 Clang 作为 C/C++ 编译器时,设置优化选项方法
c语言·c++·visual studio
MXsoft6187 小时前
云原生运维在 2025 年的发展蓝图
运维·服务器·数据库
菜鸡中的奋斗鸡→挣扎鸡7 小时前
第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
c语言·c++·蓝桥杯