线程知识应用总结

一 线程池实现

        线程池,内存池,进程池都是在用空间换时间,因为我们进程在运行时可能受到大量请求,这个时候要创建线程去运行,如果是来一个任务才创建线程,此时完成任务的时间就要算上创建线程,可是如果我们在没任务时候提前创建好线程,就能比较快的响应请求,今天我们就聊聊线程池的小实现。

        其实还是一种变形的生产消费模型。客户端发任务到消息队列,线程池从消息队列拿任务去执行,显然超市就是任务队列,客户端是生产者,线程池是消费者,应该算是单生产多消费的模型。

大致代码文件如下。

 1 main.cc

可以先往队列中push一个数字,看看程序跑起来有没有问题,没问题再将数字改成一个Task类。

2 线程池实现

        一个vp_用于保存多线程的id,tasks是个任务队列,多线程并发访问从中获得任务

        任务队列我们就设计成阻塞队列。线程同步解析-CSDN博客   这篇博客中曾提及阻塞队列和环形队列,它们在访问队列的设计时不一样的,这就导致实现同步的方式不一样,例如环形队列的空间是固定开辟好的,不方便判空和判满,如果用条件变量来实现同步,判空判满会很麻烦,大家可以下去试试。

       我们阻塞队列实现同步用条件变量,而且是两个条件变量,生产者和消费者不可以共用一个条件变量的,这个在上面那篇博客中也提过。我试想了一下用信号量不太行,因为我们在阻塞队列是直接Push和pop的,如果pop将队列变为空,此时空间信号量和数据信号量都归零了,所以如果要用信号量就得像环形队列那样访问队列。

        阻塞队列中生产者和消费者无法并发访问任务队列,所以只有一把锁,因为我们是复用了stl容器的push和pop,这个是线程不安全的,必须要加锁使用,环形队列没有复用stl的接口而且配合信号量,所以才可以用两把锁,可以让push和pop并发。下面是一些基本函数封装

#define NUM 5
template<class T>
class threadPool
{
public:
    threadPool(int size = NUM)
    :vp_(size)
    {
        pthread_mutex_init(&mutex_,nullptr);
        pthread_cond_init(&Consumer,nullptr);
        pthread_cond_init(&Productor,nullptr);
    }
     ~threadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&Consumer);
        pthread_cond_destroy(&Productor);
    }
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
   
    bool Full()
    {
        return tasks_.size() == NUM;
    }
    bool Empty()
    {
        return tasks_.size() == 0;
    }
   
   
    std::queue<T> tasks_;  任务队列
    std::vector<pthread_t> vp_; 线程池,不能存指针,内部要解引用访问的
    pthread_mutex_t mutex_;
    pthread_cond_t Consumer;
    pthread_cond_t Productor;
};

        线程执行函数

    static void* threadRun(void* arg) 这个函数必须为静态的,否则参数会不匹配
    {
            线程分离,懒得join了
        pthread_detach(pthread_self());
        threadPool<T>* tp = static_cast<threadPool<T>*> (arg);

        取出一个数据进行读取或者取一个任务来执行

        T data;
        tp->pop(data);
        std::cout<<data.getformat()<<std::endl;
           执行任务
        data();
        std::cout<<data.getRformat()<<"id: "<<pthread_self()<<std::endl;
    }
    void start()
    {
        for(int i = 0; i < NUM; i++)  创建多线程
        {
            pthread_create(vp_[i],nullptr,threadRun,this);
                                    为了threadRun可以访问类内成员,只能把this传入了
        }
    }

push和pop函数介绍,push函数由于我们是单个生产者,可以加锁也可以不加。

     void push(const T&data)
    {
        Lock();
        while(tp->Full())
        {
            pthread_cond_wait(&tp->Productor,&tp->mutex_);
        }
        tasks_.push(data);
        pthread_cond_signal(&Consumer); 唤醒消费者
        Unlock();
    }

    防止多个线程并发pop,要加锁控制
    void pop(T& data)
    {
        Lock();
         //检查是否有任务
        while(tp->Empty())
        {
            pthread_cond_wait(&tp->Consumer,&tp->mutex_);
        }
        data = tasks_.front();
        pthread_cond_signal(&Productor); 
        Unlock();
    }

        这就是所有的代码了,不过还可以改进,因为我之前写过对锁和创建线程的原生线程库接口进行封装,可以添加到这里。

二 线程池优化

class Mutex
{
public:
    Mutex(pthread_mutex_t* lock)
    :lock_(lock)
    {
        ;
    }
    ~Mutex()
    {
        ;
    }
    void Lock()
    {
        pthread_mutex_lock(lock_);//加锁
    }
    void Unlock()
    {
        pthread_mutex_unlock(lock_);//解锁
    }
private:
    pthread_mutex_t* lock_;
};
class LockGuard
{
public:
    LockGuard(pthread_mutex_t* lock)
    :mt_(lock)
    {
        mt_.Lock();
    }
    ~LockGuard()
    {
        mt_.Unlock();
    }
private:
    Mutex mt_;
}; 
class Thread
{
public:
    typedef enum
    {
        NEW = 1,
        RUNING,
        EXIT
    }status;
    typedef void* (*fun_t)(void*);
    Thread()
    {
        ;
    }
    Thread(int num, fun_t fun, void* arg)
    :id_(0),fun_(fun),arg_(arg),status_(NEW)
    {
        name_ = "thread->" + std::to_string(num);
    }
    ~Thread()
    {
        ;
    }
    static void * threadRun(void*arg)
    {
        Thread* th = (Thread*) arg;
        th->fun_(th->arg_);
        return nullptr;
    }
    void Run()
    {
        int n = pthread_create(&id_,nullptr,threadRun,(void*)this);
        if(n != 0)//成功返回0,不成功返回错误码
            exit(4);
        status_ = RUNING;    
    }
    void join()
    {
        pthread_join(id_,nullptr);
        status_ = EXIT;
    }
    std::string getname()
    {
        return name_;
    }
    int getstatus()
    {
        return status_;
    }
    pthread_t  getid()
    {
        if(status_ == RUNING)
            return id_;
        else
        {
            std::cout<<name_<<" not create ";
            return 1;
        }    
    }
    pthread_t id_;
    std::string name_;//线程名
    status status_;//线程状态
    fun_t fun_;//线程执行函数
    void* arg_;//线程参数
};

template <class T>
class threadPool
{
public:
    threadPool(int size = NUM) // vp_存的自定义类型要有默认构造,不然这里初始化会找不到默认构造!
        : vp_(size)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&Consumer, nullptr);
        pthread_cond_init(&Productor, nullptr);
    }
    void init()
    {
        for (int i = 0; i < NUM; i++) 复用Thread类,将线程执行函数和this指针传入
            用来给Thread类内访问threadPool的静态成员函数threadRun。
        {
            vp_[i] = (Thread(i, threadRun, this));
        }
    }
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    static void *threadRun(void *arg)
    {
        threadPool<T> *tp = static_cast<threadPool<T> *>(arg);

        while (true)
        {
            T data;
            tp->pop(data);
            std::cout << data.getformat() << std::endl;
            data();
            std::cout << data.getRformat() << "id: " << pthread_self() << std::endl;
        }
    }
    bool Full()
    {
        return tasks_.size() == NUM;
    }
    bool Empty()
    {
        return tasks_.size() == 0;
    }
    void start()
    {
        for (auto &e : vp_) 复用Thread Run方法创建线程
        {
            e.Run();
        }
    }
    ~threadPool()
    {
        for (auto &e : vp_)复用Thread join方法回收线程
        {
            e.join();
        }
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&Consumer);
        pthread_cond_destroy(&Productor);
    }
    void push(const T &data)
    {
        // Lock();
        {
            LockGuard lg(&mutex_); 复用LockGuard类进行加锁
            while (Full())
            {
                pthread_cond_wait(&Productor, &mutex_);
            }
            tasks_.push(data);
        }
        pthread_cond_signal(&Consumer);
        // Unlock();
    }
    void pop(T &data)
    {
        // Lock();
        {
            LockGuard lg(&mutex_);
            // 检查是否有任务
            while (Empty())
            {
                pthread_cond_wait(&Consumer, &mutex_);
            }
            data = tasks_.front();
            tasks_.pop();
        }
        pthread_cond_signal(&Productor);
        // Unlock();
    }
    std::queue<T> tasks_;  
    std::vector<Thread> vp_; 
    pthread_mutex_t mutex_;
    pthread_cond_t Consumer;
    pthread_cond_t Productor;
};

三 单例线程池

          因为我们想让程序中只有一个线程池,先了解了解懒汉,饿汉模式先,懒汉模式是等你要使用时再创建,而饿汉模式则是立刻创建,使用时直接用。我们下面就实现一个懒汉模式。

template <class T>
class threadPool
{
private:
    threadPool(int size = NUM)
        : vp_(size)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&Consumer, nullptr);
        pthread_cond_init(&Productor, nullptr);
    }
    void init()
    {
        for (int i = 0; i < NUM; i++)
        {
            vp_[i] = (Thread(i, threadRun, this));
        }
    }
    ~threadPool()
    {
         for (auto &e : vp_) // 复用Thread join方法回收线程
        {
            e.join();
        }
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&Consumer);
        pthread_cond_destroy(&Productor);
    }
    static threadPool<T>* getthreadPool()
    {
         if(nullptr == tp_)
        {
            LockGuard lg(&Poolmutex_);
            if (tp_ == nullptr)
           {       
                tp_ = new threadPool<T>;
                tp_->init(); 
                tp_->start();   
           }
        }
        return tp_;    
    }
    std::queue<T> tasks_;    
    std::vector<Thread> vp_; 
    pthread_mutex_t mutex_;
    pthread_cond_t Consumer;
    pthread_cond_t Productor;
    static threadPool<T>* tp_;
    static pthread_mutex_t Poolmutex_;
};
template<class T>
threadPool<T>*threadPool<T>::tp_ = nullptr;
template<class T>
pthread_mutex_t threadPool<T>::Poolmutex_ = PTHREAD_MUTEX_INITIALIZER;

        构造函数,拷贝,赋值私有,这样就不能new,不能创建静态对象,也不能在栈上创建对象了,析构可以私有,那就得再实现一个静态函数Destroy,内部封装delete tp_,就可以释放资源了。

让外部获取该对象,显然这里的if判断会有线程安全的问题,等会解决。

然后还要初始化和start一下。

加锁保护,那就要搞一把静态锁了,不然静态成员函数怎么访问呢?创建对象?我们现在可是在搞单例啊。

        但是没必要都加锁,当对象已经创建好了,直接返回就好了,就没必要获取锁了,可以外层再套个if,这个if不用担心线程安全。

四 常见锁介绍

        悲观锁,一旦可能出现线程安全就加锁,总是悲观地认为其它线程会修改自己的数据,所以我们上面实现的基本上都是悲观锁,乐观锁,认为其它线程不会修改数据,不加锁,但是在更新数据的时候判断数据有没有被修改,如何判断呢,1 采用版本号,也就是读取数据的时候会伴随着读取它的版本号,如果修改了数据内容,就要更改版本号,这样如果有人拿着1.0版本的数据算出来的结果,要写入时发现数据的版本号已经变成2.0了,就不能写了,此时会重新读取数据,重新计算,然后再写入。2 采用CAS,也就是会将先前取得的数据和内存数据做对比,相等则用新数据更新,不相等则不断重试。

        自旋锁:自旋锁就是申请不到,不是去阻塞,而是继续申请,因为有可能锁很快就可以给你了,但是你却非要去阻塞,为了效率就设计出不让你去阻塞,而是让你继续申请的锁。同理如果是直接去阻塞的说明这个场景下并不会很快申请到锁,

函数如下图,trylock函数是非阻塞式,可是lock不才是非阻塞式一直申请吗,因为我们感知不到轮询这个过程,我们只知道线程一直卡在lock函数处,我们以为它在阻塞,实际上是在轮询,非阻塞就是不轮询,直接返回。

读写锁

        读加锁

        写加锁

        读者写者问题分析,首先读者和读者是无关系的,因为读者不修改数据,真的只是读,但是cp问题中的的读者是要拿走数据的,这两种读不是一个概念,还有读和写也是互斥和同步关系,写和写则是互斥。至于如何使用这些函数,很简单,读的时候就用读加锁,写的时候就用写加锁呗。

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小何只露尖尖角

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值