Linux---线程池

1. 线程池概念及应用场合

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

线程池的价值

  1. 有任务,立马有线程进行服务,省掉了线程创建的时间
  2. 有效防止,server中线程过多,导致系统过载的问题。

线程池的应用场景:

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。
    但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

2. 线程池完整代码

让main.cc来充当server端,不断的往任务队列中派送任务,然后线程池里面的线程不断的去任务队列中拿任务,进行处理。

ThreadPool.hpp

#pragma once 

#include<iostream>
#include<queue>
#include<math.h>
#include<unistd.h>

#define NUM 5

class Task
{
  public:
    int base;
  public:
    Task()
    {}

    Task(int _b):base(_b)
    {}



    void Run()
    {
      std::cout<<"thread is ["<< pthread_self() <<"] task run ... done: base# "<< base << " pow is # "<< pow(base,2)<<std::endl;
    }
    ~Task()
    {}
};

class ThreadPool
{
  private:
    std::queue<Task*> q; //线程池中需要一个任务队列,server端不断的发数据,线程池中的线程不断的处理数据,那么有可能你的Task越来越大,导致性能的损失,所以为了避免改用指针
    int max_num;//你需要线程池里面有多少个线程
    pthread_mutex_t lock;
    pthread_cond_t cond; //你需要server和线程池中的线程保持同步的关系,当没有任务的时候,需要让线程等待,但是不可以让server端等待,如果它等待问题就大了

  public:
    void LockQueue()
    {
      pthread_mutex_lock(&lock);
    }

    void UnlockQueue()
    {
      pthread_mutex_unlock(&lock);
    }

    bool isEmpty()
    {
      return q.size() == 0;
    }

    void ThreadWait()
    {
      pthread_cond_wait(&cond,&lock);
    }

    void ThreadWakeUp()
    {
      pthread_cond_signal(&cond);
    }
  public:
    ThreadPool(int _max = NUM):max_num(_max)
    {}
    //加了static 那么这个成员函数就属于类,只有一份,也就没有了this指针
    //且static只能够访问static函数,没有的不能访问
    static void *Routine(void *arg)// 此时他作为内部成员函数,第一个默认的参数是this指针,所以这里其实是2个参数   正常来说pthread_create的最后一个参数应该是一个,但是此时却变为了2个
    {
      ThreadPool* this_p = (ThreadPool*)arg;
      //这是线程池里面的线程,要他们一直都存在,不销毁
      while(true)
      {
        //线程池创造出来的目的就是去解决任务,但是又面临这同组线程之间的竞争以及在取任务的时候,server端放,所以需要一把锁
        this_p->LockQueue();
        while(this_p->isEmpty())//这里有可能是会被假唤醒的,所以需要循环的去判断
        {
          this_p->ThreadWait(); 
        }
        //如果任务队列中不是空,那就开始消化任务
        Task t;
        this_p->Get(t);
        this_p->UnlockQueue();
        t.Run();//这一步很厉害,我已经把任务从队列中拿出来了,就不需要在占有锁,在临界资源内处理了,可以先进行释放,让任务队列继续工作,我自己处理我拿出来的任务
      }
    }
    void ThreadPoolInit()
    {
      pthread_mutex_init(&lock,nullptr);
      pthread_cond_init(&cond,nullptr);

      pthread_t t[NUM];
      for(int i = 0;i<max_num;++i)
      {
        pthread_create(t+i,nullptr,Routine,this);
      }
    }
    //server
    void Put(Task& in)
    {
      //要往任务队列中塞任务
      LockQueue();
      q.push(&in);
      UnlockQueue();
      
      //至少要唤醒一个线程
      //pthread_cond_signal   pthread_cond_broadcast()唤醒所有的线程
      ThreadWakeUp();
    }

    void Get(Task& out)
    {
      //调用这个函数的时候他本来就已经在临界资源了,所以不需要在加锁了
      Task* t = q.front();
      q.pop();
      out = *t;
    }


    ~ThreadPool()
    {
      pthread_mutex_destroy(&lock);
      pthread_cond_destroy(&cond);
    }
};

main.cc

#include"ThreadPool.hpp"

int main()
{
  ThreadPool* tp = new ThreadPool();
  tp->ThreadPoolInit();

  //server
  while(true)
  {
    int x = rand() % 10 + 1;
    Task t(x);
    tp->Put(t);
    sleep(1);
  }
  return 0;
}

在这里插入图片描述

3. 惊群效应

pthread_cond_broadcast(&cond);一次将所有的线程都唤醒。

惊群效应(thundering herd)是指多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群效应

惊群效应消耗了什么?
对于CPU来说增加了负载,对于操作系统来说增加了调度的压力。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值