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,结果可行,否则不可信
};
相关推荐
EterNity_TiMe_2 分钟前
【C++高阶】C++类型转换全攻略:深入理解并高效应用
开发语言·c++·学习·性能优化·学习方法
特立独行的猫a3 分钟前
C/C++ 中的算术运算及其陷阱(详解,举例分析)
c语言·c++·算法
沐杉53912 分钟前
青岛实训 8月21号 day33
linux·运维·服务器
小字节,大梦想1 小时前
【数据结构】详细介绍各种排序算法,包含希尔排序,堆排序,快排,归并,计数排序
c语言·数据结构·c++·算法
笑笑布丁1 小时前
一些python实践
linux·python
Mryan20051 小时前
OpenJudge | 寻找中位数
开发语言·数据结构·c++·算法·openjudge
椰椰椰耶2 小时前
【Linux】常用的命令
linux·运维·服务器
xiaojiesec3 小时前
第145天:内网安全-Linux权限维持&Rootkit后门&Strace监控&Alias别名&Cron定时任务
linux·运维·安全
请揣满RMB3 小时前
Qt常用控件——QRadioButton和QCheckBox
开发语言·c++·qt
安全在心中4 小时前
python-网页自动化(三)
运维·python·自动化