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,结果可行,否则不可信
};
相关推荐
若亦_Royi12 分钟前
C++ 的大括号的用法合集
开发语言·c++
2301_819287121 小时前
ce第六次作业
linux·运维·服务器·网络
CIb0la1 小时前
GitLab 停止为中国区用户提供 GitLab.com 账号服务
运维·网络·程序人生
武汉联从信息1 小时前
如何使用linux日志管理工具来管理oracle osb服务器日志文件?
linux·运维·服务器
天天进步20151 小时前
STUN服务器实现NAT穿透
运维·服务器
PieroPc2 小时前
Python 自动化 打开网站 填表登陆 例子
运维·python·自动化
Aileen_0v02 小时前
【AI驱动的数据结构:包装类的艺术与科学】
linux·数据结构·人工智能·笔记·网络协议·tcp/ip·whisper
州周2 小时前
Ftp目录整个下载
linux·服务器·数据库
Jackey_Song_Odd2 小时前
Ubuntu 24.04.1 解决部分中文字符(门、径)显示错误的问题
linux·ubuntu
kaixin_learn_qt_ing3 小时前
Linux export命令
linux