线程池+读写锁

读写锁介绍

读写锁: 为了处理多线程中读数据比写数据更频繁(读多写少),给读加锁会带来效率降低的问题,引入了一种新的锁——读写锁。读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
自旋锁: 对应自旋锁,只有一个线程获得锁资源(与互斥锁),其他未得到锁资源的线程不是挂起等待,而是处于自旋状态,不断去检测锁的状态(自旋锁应用于线程占在临界区内待的时间特别短的场景)

特点:

  • 三种关系: 读者和读者(共享)、读者和写者(互斥和同步) 和 写者和写者(互斥)
  • 两个角色: 读者和写者
  • 一份资源: 写操作,读读取

生产消费模型和读写锁的区别:

读写锁中读者不会拿走数据,但生产消费模型中的消费者会拿走数据,所以读写锁中读者与读者直接是可以共享数据

读写锁的三种同步方案:

读优先: 想尽一切办法让读者先读。当前为读锁,读者可直接进入,为写锁,当前为写锁,写者写完之后,让读者先进入读(可能会造成写饥饿问题)
写优先: 想尽一切办法让写者先写。当前为读锁或写锁,写者都不可以进入,读者读完或写者写完之后,写者可以先进入写
公平占有锁: 读者写者公平竞争锁

线程池介绍

线程池: 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,简单说:提前把线程创建好,等待任务的派发。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量

线程池的价值:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。可同时处理多任务,多请求。
  2. 有任务可以立即从线程池中调取线程取处理,节省了线程创建的时间
  3. 有效防止服务端线程过多而导致系统过载的问题

线程池与进程池:

  • 线程池占用资源少,但是健壮性(鲁棒性)不强
  • 进程池占用资源多,但是健壮性(鲁棒性)更强(涉及进程间通信,可利用管道的阻塞队列实现进程间同步和互斥)

实现

线程池中首先需要有很多个线程,用户可以自己选择创建多少个线程。为了实现线程间的同步与互斥,还需要增加两个变量——互斥量和条件变量。我们还需要一个任务队列,主线程不断往里面塞任务,线程池的线程不断去处理。需要注意的是:这里的任务队列可以为空,但不能满,所以任务队列的容量不限定

线程池的四个成员变量:

  • 一个队列: 存放任务
  • 线程池中线程数: 记录线程池中创建的线程数
  • 互斥量: 一个互斥锁
  • 条件变量: 队列为空时的条件变量

Mutex.hpp 锁的封装

#pragma once

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

class Mutex
{
public:
    Mutex(pthread_mutex_t *lock_p = nullptr): lock_p_(lock_p)
    {}
    void lock()
    {
        if(lock_p_) pthread_mutex_lock(lock_p_);
    }
    void unlock()
    {
        if(lock_p_) pthread_mutex_unlock(lock_p_);
    }
    ~Mutex()
    {}
private:
    pthread_mutex_t *lock_p_;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex): mutex_(mutex)
    {
        mutex_.lock(); //在构造函数中进行加锁
    }
    ~LockGuard()
    {
        mutex_.unlock(); //在析构函数中进行解锁
    }
private:
    Mutex mutex_;
};

ThreadPool.hpp 主要框架(唤醒和等待操作都已经封装好)

#pragma once
#include "Thread.hpp"
#include "Mutex.hpp"
#include <vector>
#include <queue>
#include <pthread.h>
#include <mutex>
using namespace wzh;
const int gnum = 5;
template <class T>
class ThreadPool;
template <class T>
class ThreadData
{
public:
    ThreadPool<T> *threadpool;
    std::string name;
    ThreadData(ThreadPool<T> *tp, const std::string &n)
        : threadpool(tp), name(n)
    {
    }
};
template <class T>
class ThreadPool
{
    // 单例模式要把构造设置为私有,不能没有!
private:
    ThreadPool(const int num = gnum)
        : _num(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        for (int i = 0; i < _num; ++i)
        {
            _threads.push_back(new Thread());
        }
    }
    // 赋值重载不能写
    void operator=(const ThreadPool &) = delete;
    // 拷贝构造也不能写
    ThreadPool(const ThreadPool &) = delete;

public:
    // 属于线程池的类内成员,需要加static
    // 既然是static就不能访问类内的成员方法
    static void *handlerTask(void *args)
    {
        ThreadData<T> *tp = (ThreadData<T> *)args;
        CalTask t;
        while (true)
        {
            {
                // tp->threadpool->LockQueue();
                LockGuard lock(tp->threadpool->mutex());
                while (tp->threadpool->IsQueueEmpty())
                {
                    tp->threadpool->ThreadWait();
                }
                t = tp->threadpool->pop();
                // 不能这样写
                // 这样就是加锁 拿任务 处理 解锁,这样会导致处理任务是串行的
                //  t(); //处理任务 bug
                //  tp->threadpool->UnLockQueue();
            }
            cout << tp->name << " 处理完了这个任务 " << t.toTaskstring() << " 并处理完成 " << t() << endl;
        }
        delete tp;
        return nullptr;
    }
    void LockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }
    void UnLockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }
    bool IsQueueEmpty()
    {
        return _task_queue.empty();
    }
    void ThreadWait()
    {
        pthread_cond_wait(&_cond, &_mutex);
    }
    pthread_mutex_t *mutex()
    {
        return &_mutex;
    }
    void start()
    {
        for (const auto &t : _threads)
        {
            ThreadData<T> *td = new ThreadData<T>(this, t->threadname());
            t->start(handlerTask, td);
            cout << t->threadname() << "  start..." << endl;
        }
    }
    // 向队列push任务
    void Push(const T &in)
    {
        LockGuard lock(&_mutex);
        _task_queue.push(in);
        // 环形在特定条件变量下等待的线程
        pthread_cond_signal(&_cond);
    }
    T pop()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for (const auto &t : _threads)
        {
            delete t;
        }
    }
    // 获取单例
    // 单例,一个类一个对象,所以设置static
    // 懒汉模式
    static ThreadPool<T> *getInstance()
    {
        // tp是静态成员,所有使用该类的线程都能访问到tp,他是临界资源,要加锁
        // 提前判断,可以避免上来就竞争锁
        if (tp == nullptr)
        {
            _signlelock.lock();
            if (tp == nullptr)
            {
                tp = new ThreadPool<T>();
            }
            _signlelock.unlock();
        }
        return tp;
    }

private:
    int _num;                  // 创建线程的个数
    vector<Thread *> _threads; // 存放线程
    queue<T> _task_queue;      // 从队列里拿任务,共享资源,需要加锁保护
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
    static ThreadPool<T> *tp;
    static std::mutex _signlelock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::tp = nullptr;
template <class T>
std::mutex ThreadPool<T>::_signlelock;

注意:

  • 线程池中的任务队列存放的是任务指针类型,节省任务队列的空间开销
  • 线程池的初始化操作不在构造函数中进行,而是提供了一个初始化接口(设计模式—单例模式),让用户自行调用。这是为了不让构造函数做有风险的事情(如果函数调用失败,会导致线程池创建失败)

thread.hpp 封装线程

需要注意的是,创建一个线程还需要提供一个线程启动后要执行的函数,这个启动函数只能有一个参数。如果把这个函数设置为成员函数,那么这个函数的第一个参数默认是this指针,这样显然是不可行的,所以这里我们考虑把这个启动函数设置为静态的。但是设置为静态的成员函数又会面临一个问题:如何调用其他成员函数和成员变量? 所以这里我们考虑创建线程的时候,把this指针传过去,让启动函数的arg 参数去接收即可

#pragma once
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <string>
#include <unistd.h>
#include <functional>
#include <cassert>
#include <pthread.h>
using namespace std;
namespace wzh
{
    class Thread
    {
    private:
        static void *start_routine(void *args) // 类内成员,使用static没有this指针了
        {
            // 把对应的方法再做一下包   装
            Thread *ctx = (Thread *)args;
            return ctx->callback();
            //        静态方法不能调用成员方法,把成员变量变量变为static可以,但不是好方法!
            //        return func_(args);
        }
    public:
        typedef function<void *(void *)> func_t;
        const int num = 1024;
        Thread()
        {
            char namebuffer[num];
            snprintf(namebuffer, sizeof(namebuffer), "thread-%d", threadnum++);
            name_ = namebuffer;
        }
        void start(func_t func, void *args = nullptr)
        {
            func_=func;
            args_=args;
            int n = pthread_create(&tid_, nullptr, start_routine, this);
            assert(n == 0);
            (void)n;
        }
        void *callback()
        {
            return func_(args_);
        }
        void join()
        {
            int n = pthread_join(tid_, nullptr);
            assert(n == 0);
            (void)n;
        }
        ~Thread()
        {
            // do nothing
        }
        std::string threadname()
        {
            return name_;
        }
    private:
        string name_;
        pthread_t tid_;
        func_t func_;
        void *args_;
        static int threadnum;
    };
int Thread::threadnum=1;
    void *getTicket(void *args)
    {
        string work_type = (char *)args;
        while (true)
        {
            cout << "我是一个新线程,我正在做:" << work_type << endl;
            sleep(1);
        }
    }
}

**注意:**线程创建后,执行启动函数,在这个函数中,线程会去任务队列中取任务并处理,取任务前需要进行加锁的操作(如果队列为空需要挂起等待),取完任务然后进行解锁,然后处理任务,让其它线程去任务队列中取任务

task.hpp 构造任务

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <functional>
class CalTask
{
    using func_t=std::function<int(int,int,char)>;
    // typedef std::function<int(int,int,char)> func_t;
public:
    CalTask()
    {}
    CalTask(int x,int y,char op,func_t func)
            :_x(x)
            ,_y(y)
            ,_op(op)
            ,_func(func)
    {}
    std::string operator()()
    {
        int res=_func(_x,_y,_op);
        char buffer[64];
        snprintf(buffer,sizeof(buffer),"%d %c %d = %d",_x,_op,_y,res);
        return buffer;
    }
    std::string toTaskstring()
    {
        char buffer[64];
        snprintf(buffer,sizeof(buffer),"%d %c %d = ?",_x,_op,_y);
        return buffer;
    }
    private:
    int _x;
    int _y;
    char _op;
    func_t _func;
};
class SaveTask
{
    using func_t=std::function<void(const std::string&)>;
public:
    SaveTask()
    {}
    SaveTask(std::string& message,func_t func)
            :_message(message)
            ,_func(func)
    {}
    void operator()()
    {
        _func(_message);
    }
private:
    std::string _message;
    func_t _func;
};
void save(const std::string& message)
{
    const std::string target="log.txt";
    FILE* fp=fopen(target.c_str(),"a+");
    if(fp==nullptr)
    {
        perror("fopen");
        return ;
    }
    fputs(message.c_str(),fp);
    fputs("\n",fp);
    fclose(fp);
}
const std::string oper = "+-*/%";
int mymath(int x, int y, char op)
{
    int result = 0;
    switch (op)
    {
        case '+':
            result = x + y;
            break;
        case '-':
            result = x - y;
            break;
        case '*':
            result = x * y;
            break;
        case '/':
        {
            if (y == 0)
            {
                std::cerr << "div zero error!" << std::endl;
                result = -1;
            }
            else
                result = x / y;
        }
            break;
        case '%':
        {
            if (y == 0)
            {
                std::cerr << "mod zero error!" << std::endl;
                result = -1;
            }
            else
                result = x % y;
        }
            break;
        default:
            // do nothing
            break;
    }

    return result;
}

test.cpp 主线程

主线程负责创建线程池,然后塞任务即可

#include "Thread.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
#include<memory>
#include<unistd.h>
int main()
{
    // std::unique_ptr<ThreadPool<CalTask>> tp(new ThreadPool<CalTask>());
    ThreadPool<CalTask>::getInstance()->start();
    // tp->start();
    while(1)
    {
        int x,y;
        char op;
        cout << "请输入数据1" << endl;
        cin >> x ;
        cout << "请输入数据2" << endl;
        cin >> y;
        cout << "请输入运算符" << endl;
        cin >> op;
        CalTask t(x,y,op,mymath);
        ThreadPool<CalTask>::getInstance()->Push(t);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值