精华内容
下载资源
问答
  • 线程池和内存池

    2018-11-25 15:40:24
    文章目录一、线程池1、线程池的概念2、线程池的组成部分3、线程池的流程4、线程池的Demo5、线程池的应用二、线程池的惊群效应1、惊群效应的...的特点四、线程池和内存池的相关问题1、线程池大小应该设置为多少...

    一、线程池

    1、线程池的概念

      服务器程序利用线程技术响应客户请求已经司空见惯,但是线程的使用是有待优化和处理的。单线程执行并不是一个高效的方式,这个时候可能就要考虑高并发,多线程等方式。线程池也是线程优化的一种方式

      在面向对象的过程中,对象的创建和销毁是非常占资源的,每创建一个对象都要获取内存资源以及其他一些资源。这就产生了“池化技术”。

    【线程池如何提高服务器程序的性能?】

    • T1 = 创建线程;
    • T2 = 执行线程 包括访问共享数据、线程同步等;
    • T3 = 销毁线程;
    • T = T1 + T2 + T3。

      单线程的情况下,系统花大量的时间再T1、T3阶段。我们要采取最优的措施,减少T1和T3的系统开销。线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目。

    2、线程池的组成部分

    (1) 线程管理器:用于创建并管理线程池。

    (2)工作线程:线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在池中,这些初始化的线程一般处于空闲状态。

    (3)任务接口:每个任务必须实现的接口。当线程池的任务队列中有可执行任务时,被空间的工作线程调去执行(线程的闲与忙的状态是通过互斥量实现的),把任务抽象出来形成一个接口,可以做到线程池与具体的任务无关。

    (4)任务队列:用来存放没有处理的任务。提供一种缓冲机制。实现这种结构有很多方法,常用的有队列和链表结构。

    在这里插入图片描述

    3、线程池的流程

    1. 调用pthread_create()创建若干线程,置入线程池。

    2. 调用pthread_cond_wait()等待任务队列不为空的条件上;任务到达时,从线程池取空闲线程处理任务队列中的任务;调用pthread_cond_signal()通知可以有新任务添加进来。

    3. 调用pthread_cond_wait()等待任务队列不为满的条件上;向任务队列中添加任务;调用pthread_cond_signal()通知线程池中的线程去处理任务。

    4. 调用pthread_detach()销毁线程池中的线程。

    // 伪代码
    int main(void)
    {
        threadpool_t *thp = pthreadpool_create();
        for (int i = 0; i < max_pool; ++i)
        {
            num[i] = i;
            threadpool_add(thp, process, (void *)&num[i]);
        }
        threadpool_destroy(thp);
        return 0;
    }
    
    // 任务函数
    void *process(void *arg) { }
    
    // 创建线程池并做一些初始化的工作
    threadpool_t *pthreadpool_create()
    {
        init();
        for (i = 0; i < max_thr_num; ++i)
        {
            pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);
        }
    }
    // 线程池中的每个线程或者终止或者处理任务
    void *threadpool_thread()
    {
        while (true)
        {
            // 如果任务队列为空,则调用wait阻塞在等待条件上,当wait被调用时说明有任务,则退出while
            while ( (pool->queue_size == 0))
            {
               pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
            }
            /* 取任务 */
            // 通知可以有新的任务添加进来
            pthread_cond_signal(&(pool->queue_not_full));
            /* 执行任务 */
        }
    }
    // 向任务队列中添任务并通知线程池去处理任务
    int threadpool_add()
    {
        // 如果任务队列为满,则调用wait阻塞在等待条件上,当wait被调用时说明队列不为空,则退出while
        while ( (pool->queue_size == pool->queue_max_size) && (!pool->shutdown) )
        {
            pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
        }
        // 添加完任务后,任务队列不为空,通知线程池中的一个线程去处理任务
        pthread_cond_signal(&(pool->queue_not_empty));
    
    }
    // 销毁线程池
    void threadpool_destroy() { }
    

    在这里插入图片描述

    4、线程池的Demo

    #include <stdlib.h>
    #include <stdio.h>
    #include <pthread.h>
    #include <string.h>
    #include <unistd.h>
    
    #define true 1
    #define false 0
    
    typedef struct 
    {
        void *(*function)(void *);  // 函数指针,回调函数
        void *arg;  // 上面函数的参数
    }threadpool_task_t; // 各子线程任务结构体
    
    struct threadpool_t
    {
        pthread_mutex_t lock;           // 线程池的锁(锁住本结构体)
        pthread_mutex_t thread_counter; // 记录忙状态线程个数的锁
    
        pthread_cond_t queue_not_full;  // 任务队列满时,线程池中的线程阻塞,等待此条件变量
        pthread_cond_t queue_not_empty; // 任务队列不为空时,通知等待任务的线程
    
        pthread_t *threads;             // 线程池(存放的是每个线程的id)
        pthread_t adjust_tid;           // 管理者线程(管理线程池)
        threadpool_task_t *task_queue;  // 任务队列(数组首地址)
    
        int min_thr_num;        // 线程池最小线程数
        int max_thr_num;        // 线程池最大线程数
        int busy_thr_num;       // 忙状态线程数
        int live_thr_num;       // 当前存活的线程数
        int wait_exit_thr_num;  // 要销毁的线程数
    
        int queue_front;        // 任务队列的队头
        int queue_rear;         // 任务队列的队尾
        int queue_size;         // 任务队列的实际任务数
        int queue_max_size;     // 任务队列可容纳任务数的上限
    
        int shutdown;           // 标志位,线程池使用状态,true或者false
    };
    
    void threadpool_free(threadpool_t *pool);
    void *threadpool_thread(void *threadpool);
    void *adjust_thread(void *threadpool);
    
    // 创建线程池并做一些初始化的工作
    threadpool_t *pthreadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)
    {
        int i;
        threadpool_t *pool = NULL;
        do
        {
            if ( (pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL )
            {
                printf("malloc threadpool fail\n");
                break;
            }
    
            // 初始化一些基本参数
            pool->min_thr_num = min_thr_num;
            pool->max_thr_num = max_thr_num;
            pool->busy_thr_num = 0;
            pool->live_thr_num = min_thr_num;
            pool->wait_exit_thr_num = 0;
            pool->queue_size = 0;
            pool->queue_max_size = queue_max_size;
            pool->queue_front = 0;
            pool->queue_rear = 0;
            pool->shutdown = false;
    
            // 初始化线程池,开辟数组空间
            pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * max_thr_num);
            if (pool->threads == NULL)
            {
                printf("malloc threads fail\n");
                break;
            }
            memset(pool->threads, 0, sizeof(pthread_t) * max_thr_num);
            
            // 初始化任务队列
            pool->task_queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t) * queue_max_size);
            if (pool->task_queue == NULL)
            {
                printf("malloc task_queue fail\n");
                break;
            }
    
            // 初始化锁和条件变量
            if (pthread_mutex_init(&(pool->lock), NULL) != 0 
                || pthread_mutex_init(&(pool->thread_counter), NULL) != 0
                || pthread_cond_init(&(pool->queue_not_empty), NULL) != 0
                || pthread_cond_init(&(pool->queue_not_full), NULL) != 0)
            {
                printf("init lock or cond fail\n");
                break;
            }
    
            // 初始化线程池和管理者线程
            for (i = 0; i < max_thr_num; ++i)
            {
                pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);
                printf("start thread %lu ...\n", pool->threads[i]);
            }
            pthread_create(&(pool->adjust_tid), NULL, adjust_thread, (void *)pool);
            return pool;
        }while(0);
    
        // 前面代码调用失败时,释放pool的空间
        threadpool_free(pool);
    }
    
    // 线程池中的每个线程或者终止或者处理任务
    void *threadpool_thread(void *threadpool)
    {
        threadpool_t *pool = (threadpool_t *)threadpool;
        threadpool_task_t task;
    
        while (true)
        {
            pthread_mutex_lock(&(pool->lock));
            // 如果任务队列为空,则调用wait阻塞在等待条件上,当wait被调用时说明有任务,则退出while
            while ( (pool->queue_size == 0) && (!pool->shutdown) )
            {
                printf("thread %lu is waitting\n", pthread_self());
                // 等待工作队列不为空的条件
                pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
                
                // 任务队列为空了,已经没有任务可以执行了,让所有线程自动终止
                if (pool->wait_exit_thr_num > 0)
                {
                    pool->wait_exit_thr_num--;
                    if (pool->live_thr_num > pool->min_thr_num)
                    {
                        printf("thread %lu is exitting\n", pthread_self());
                        pool->live_thr_num--;
                        pthread_mutex_unlock(&(pool->lock));
                        pthread_exit(NULL);
                    }
                }
            }
            
            if (pool->shutdown)
            {
                pthread_mutex_unlock(&(pool->lock));
                printf("thread %lu is exitting\n", pthread_self());
                pthread_detach(pthread_self());
                pthread_exit(NULL);
            }
            
            // 从任务队列中取任务(队头)
            task.function = pool->task_queue[pool->queue_front].function;
            task.arg = pool->task_queue[pool->queue_front].arg;
    
            // 队头元素向后移动
            pool->queue_front = (pool->queue_front + 1) % pool->queue_max_size;
            pool->queue_front++;
            
            // 通知可以有新的任务添加进来
            pthread_cond_signal(&(pool->queue_not_full));
            
            pthread_mutex_unlock(&(pool->lock));
    
            // 正在处理任务,忙状态线程数加1
            printf("thread %lu is working\n", pthread_self());
            pthread_mutex_lock(&(pool->thread_counter));
            pool->busy_thr_num++;
            pthread_mutex_unlock(&(pool->thread_counter));
    
            // 执行回调函数去处理任务
            (*(task.function))(task.arg);
            
            // 任务处理完毕,忙状态线程数减1
            printf("thread %lu is end working\n", pthread_self());
            pthread_mutex_lock(&(pool->thread_counter));
            pool->busy_thr_num--;
            pthread_mutex_unlock(&(pool->thread_counter));
        }   
        pthread_exit(NULL);
    }
    
    // 管理者线程
    void *adjust_thread(void *threadpool)
    {
        int i;
        threadpool_t *pool = (threadpool_t *)threadpool;
        
        while (!pool->shutdown)
        {
            // 每隔10秒对当前线程池进行管理
            sleep(10);
            
            // 通过加锁和解锁访问数据
            pthread_mutex_lock(&(pool->lock));
            int queue_size = pool->queue_size;
            int live_thr_num = pool->live_thr_num;
            pthread_mutex_unlock(&(pool->lock));
    
            pthread_mutex_lock(&(pool->thread_counter));
            int busy_thr_num = pool->busy_thr_num;
            pthread_mutex_unlock(&(pool->thread_counter));
    
            // 线程池扩容和瘦身
            
        }
    }
    
    // 向任务队列中添任务并通知线程池去处理任务
    int threadpool_add(threadpool_t *pool, void *(*function)(void *arg), void *arg)
    {
        pthread_mutex_lock(&(pool->lock));
        while ( (pool->queue_size == pool->queue_max_size) && (!pool->shutdown) )
        {
            pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
        }
        // 如果线程池需要关闭了, 通知线程池中的线程自动终止
        if (pool->shutdown)
        {
            pthread_cond_signal(&(pool->queue_not_empty));
            pthread_mutex_unlock(&(pool->lock));
            return 0;
        }
    
        if (pool->task_queue[pool->queue_rear].arg != NULL)
        {
            pool->task_queue[pool->queue_rear].arg == NULL;
        }
    
        pool->task_queue[pool->queue_rear].function = function;
        pool->task_queue[pool->queue_rear].arg = arg;
        pool->queue_rear = (pool->queue_rear + 1) % pool->queue_max_size;
        pool->queue_rear++;
    
        // 添加完任务后,任务队列不为空,通知线程池中的一个线程去处理任务
        pthread_cond_signal(&(pool->queue_not_empty));
        pthread_mutex_unlock(&(pool->lock));
    }
    
    // 释放所有的资源
    void threadpool_free(threadpool_t *pool)
    {
        if (pool == NULL)
        {
            return;
        }
        if (pool->task_queue)
        {
            free(pool->task_queue);
        }
        // 释放和线程池相关的资源
        if (pool->threads)
        {
            free(pool->threads);
            pthread_mutex_lock(&(pool->lock));
            pthread_mutex_destroy(&(pool->lock));
            pthread_mutex_lock(&(pool->thread_counter));
            pthread_mutex_destroy(&(pool->thread_counter));
            pthread_cond_destroy(&(pool->queue_not_empty));
            pthread_cond_destroy(&(pool->queue_not_full));
        }
        free(pool);
        return;
    }
    
    // 销毁线程池(通知线程池中的线程自我终止)
    void threadpool_destroy(threadpool_t *pool)
    {
        int i;
        if (pool == NULL)
        {
            return;
        }
        pool->shutdown = true;
    
        // 销毁管理者线程
        pthread_detach(pool->adjust_tid);
        
        for (i = 0; i < pool->live_thr_num; ++i)
        {
            // 通知所有空闲线程
            pthread_cond_signal(&(pool->queue_not_empty));
        }
        for (i = 0; i < pool->live_thr_num; ++i)
        {
            // 回收空闲线程
            pthread_detach(pool->threads[i]);
        }
        threadpool_free(pool);
        return;
    }
    
    // 实际需要处理的任务
    void *process(void *arg)
    {
        sleep(1);
        printf("hello\n");
        return NULL;
    }
    
    int main(void)
    {
        // 创建线程池,最小3个线程,最大100个线程,任务队列最大容量为100
        threadpool_t *thp = pthreadpool_create(3, 100, 100);
        printf("thread pool init\n");
        
        int num[20], i;
        for (i = 0; i < 20; ++i)
        {
            num[i] = i;
            printf("add task %d\n", i);
            // 向线程池中添加任务
            // int threadpool_add(threadpool_t *pool, void *(*function)(void *arg), void *arg)
            threadpool_add(thp, process, (void *)&num[i]);
        }
        sleep(5);
        threadpool_destroy(thp);
        return 0;
    }
    

    5、线程池的应用

    【线程池适用于】:

    • 需要大量的线程来完成任务,且完成任务的时间比较短。web服务器完成网页请求这样的任务,使用线程池技术是非常合适的。 因为单个任务小,而任务数量巨大,一个热门网站的点击次数会很多。

    • 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

    • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。

    【线程池不适用于】:

    • 如果需要使一个任务具有特定优先级。

    • 如果具有可能会长时间运行(并因此阻塞其他任务)的任务。

    • 如果需要将线程放置到单线程单元中(线程池中的线程均处于多线程单元中)。

    • 如果需要永久标识来标识和控制线程,比如想使用专用线程来终止该线程,将其挂起或按名称发现它。

    二、线程池的惊群效应

    1、惊群效应的概念

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

      为了更好的理解何为惊群,举一个很简单的例子,当你往一群鸽子中间扔一粒谷子,所有的各自都被惊动前来抢夺这粒食物,但是最终注定只可能有一个鸽子满意的抢到食物,没有抢到的鸽子只好回去继续睡觉,等待下一粒谷子的到来。这里鸽子表示进程(线程),那粒谷子就是等待处理的事件。

    2、惊群效应存在的问题

    • 系统对用户进程/线程频繁的做无效的调度、上下文切换,系统系能大打折扣。

    • 为了确保只有一个线程得到资源,用户必须对资源操作进行加锁保护,进一步加大了系统开销。

      最常见的例子就是对于socket描述符的accept操作,当多个用户进程/线程监听在同一个端口上时,由于实际只可能accept一次,因此就会产生惊群现象,当然前面已经说过了,这个问题是一个古老的问题,但目前的内核版本已经修复了这个问题,一个链接过来,内核只会唤醒一个子进程出来accept,这样就不用担心惊群效应了。

    3、线程池的惊群效应

      一个基本的线程池框架是基于生产者和消费者模型的。生产者往队列里面添加任务,而消费者从队列中取任务并进行执行。一般来说,消费时间比较长,一般有许多个消费者。当许多个消费者同时在等待任务队列的时候,也就发生了“惊群效应”。

      pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。

      但使用pthread_cond_signal不会有“惊群现象”产生,它最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。所以线程池并不会产生“惊群效应”。同时,这种方式使用多进程共享资源,等待管道或者其他资源等,提供cpu利用率。

    4、怎么判断发生了惊群

      我们根据strace的返回信息可以确定:

    • 系统只会让一个进程真正的接受这个连接,而剩余的进程会获得一个EAGAIN信号。

    • 通过返回结果和进程执行的系统调用判断。

    5、如何解决惊群效应

      Linux内核的3.9版本带来了SO_REUSEPORT特性,该特性支持多个进程或者线程绑定到同一端口,提高服务器程序的性能,允许多个套接字bind()以及listen()同一个TCP或UDP端口,并且在内核层面实现负载均衡。

      在未开启SO_REUSEPORT的时候,由一个监听socket将新接收的连接请求交给各个工作者处理,看图示:
    在这里插入图片描述

      在使用SO_REUSEPORT后,多个进程可以同时监听同一个IP:端口,然后由内核决定将新链接发送给哪个进程,显然会降低每个工人接收新链接时锁竞争。
    在这里插入图片描述

    【SO_REUSEPORT解决了什么问题】:

    • 允许多个套接字bind()/listen()同一个tcp/udp端口。每一个线程拥有自己的服务器套接字,在服务器套接字上没有锁的竞争。

    • 内核层面实现负载均衡。

    • 安全层面,监听同一个端口的套接字只能位于同一个用户下面。

    • 处理新建连接时,查找listener的时候,能够支持在监听相同IP和端口的多个sock之间均衡选择。

    三、内存池

    1、内存池的概念

      内存池(Memory Pool)是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请内存,这样做的缺点在于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

      内存池则是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。

    2、内存池的流程和设计

    1. 先申请一块连续的内存空间,该段内存空间能够容纳一定数量的对象。

    2. 每个对象连同一个指向下一个对象的指针一起构成一个内存节点(Memory Node)。各个空闲的内存节点通过指针形成一个链表,链表的每一个内存节点都是一块可供分配的内存空间。

    3. 某个内存节点一旦分配出去,从空闲内存节点链表中去除。

    4. 一旦释放了某个内存节点的空间,又将该节点重新加入空闲内存节点链表。

    5. 如果一个内存块的所有内存节点分配完毕,若程序继续申请新的对象空间,则会再次申请一个内存块来容纳新的对象。新申请的内存块会加入内存块链表中。
    在这里插入图片描述
      如上图所示,申请的内存块存放三个可供分配的空闲节点。空闲节点由空闲节点链表管理,如果分配出去,将其从空闲节点链表删除,如果释放,将其重新插入到链表的头部。如果内存块中的空闲节点不够用,则重新申请内存块,申请的内存块由内存块链表来管理。

    3、内存池的Demo

    #include <iostream>
    using namespace std;
    
    // 类模板
    template<int ObjectSize, int NumofObjects = 20>
    class MemPool
    {
    private:
    	//空闲节点结构体
    	struct FreeNode
    	{
    		FreeNode* pNext;
    		char data[ObjectSize]; // 类的大小(占用的内存)
    	};
    
    	//内存块结构体
    	struct MemBlock
    	{
    		MemBlock* pNext;
    		FreeNode data[NumofObjects];
    	};
    
    	FreeNode* freeNodeHeader;
    	MemBlock* memBlockHeader;
    
    public:
    	MemPool()
    	{
    		freeNodeHeader = NULL;
    		memBlockHeader = NULL;
    	}
    
    	~MemPool()
    	{
    		MemBlock* ptr;
    		while (memBlockHeader)
    		{
    			ptr = memBlockHeader->pNext;
    			delete memBlockHeader;
    			memBlockHeader = ptr;
    		}
    	}
    	void* malloc();
    	void free(void*);
    };
    
    //分配空闲的节点
    template<int ObjectSize, int NumofObjects>
    void* MemPool<ObjectSize, NumofObjects>::malloc()
    {
    	// 无空闲节点,申请新内存块(每一次申请的大小都是NumofObjects)
    	if (freeNodeHeader == NULL)
    	{
    		MemBlock* newBlock = new MemBlock;
    		newBlock->pNext = NULL;
    		// 设置内存块的第一个节点为空闲节点链表的首节点
    		freeNodeHeader = &newBlock->data[0];	
    		// 将内存块的其它节点串起来(将内存块挂载到链表上,newBlock->data[i]是一个FreeNode)
    		for (int i = 1; i < NumofObjects; ++i)
    		{
    			newBlock->data[i - 1].pNext = &newBlock->data[i];
    		}
    		newBlock->data[NumofObjects - 1].pNext = NULL;
    
    		// 如果是首次申请内存块
    		if (memBlockHeader == NULL)
    		{
    			memBlockHeader = newBlock;
    		}
    		else
    		{
    			// 将新内存块加入到内存块链表
    			newBlock->pNext = memBlockHeader;
    			memBlockHeader = newBlock;
    		}
    	}
    	// 如果有空闲链表节点,返回空闲链表节点的第一个节点
    	void* freeNode = freeNodeHeader;
    	// 移动空闲链表节点
    	freeNodeHeader = freeNodeHeader->pNext;
    	return freeNode;
    }
    
    //释放已经分配的节点
    template<int ObjectSize, int NumofObjects>
    void MemPool<ObjectSize, NumofObjects>::free(void* p)
    {
    	FreeNode* pNode = (FreeNode*)p;
    	pNode->pNext = freeNodeHeader;	// 将释放的节点插入空闲节点头部
    	freeNodeHeader = pNode;
    }
    
    class ActualClass
    {
    	static int count;
    	int No;
    
    public:
    	ActualClass()
    	{
    		No = count;
    		count++;
    	}
    
    	void print()
    	{
    		cout << this << ": ";
    		cout << "the " << No << "th object" << endl;
    	}
    
    	void* operator new(size_t size);
    	void operator delete(void* p);
    };
    
    // 定义内存池对象(定义内存池的元素类型和内存池大小)
    MemPool<sizeof(ActualClass), 2> mp;
    
    // 重载new和delete操作符
    void* ActualClass::operator new(size_t size)
    {
    	return mp.malloc();
    }
    
    void ActualClass::operator delete(void* p)
    {
    	mp.free(p);
    }
    
    int ActualClass::count = 0;
    
    int main()
    {
    	ActualClass* p1 = new ActualClass;
    	p1->print();
    
    	ActualClass* p2 = new ActualClass;
    	p2->print();
    	delete p1;
    
    	p1 = new ActualClass;
    	p1->print();
    
    	ActualClass* p3 = new ActualClass;
    	p3->print();
    
    	delete p1;
    	delete p2;
    	delete p3;
    
    	system("pause");
    	return 0;
    }
    

    4、内存池的特点

    • 针对特殊情况,例如需要频繁分配释放固定大小的内存对象时,不需要复杂的分配算法和多线程保护。也不需要维护内存空闲表的额外开销,从而获得较高的性能

    • 由于开辟一定数量的连续内存空间作为内存池块,因而一定程度上提高了程序局部性,提升了程序性能。

    • 比较容易控制页边界对齐和内存字节对齐,没有内存碎片的问题。

    • 当需要分配管理的内存在100M一下的时候,采用内存池会节省大量的时间,否则会耗费更多的时间。

    • 内存池可以防止更多的内存碎片的产生。

    • 更方便于管理内存。

    四、线程池和内存池的相关问题

    1、线程池大小应该设置为多少?

    在这里插入图片描述

    最佳线程数目 = ( (线程等待时间 + 线程CPU时间) / 线程CPU时间 ) * CPU数目
    

      比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:

    最佳线程数目 = (线程等待时间 / 线程CPU时间 + 1)* CPU数目
    

    【结论】:
      线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。

    2、线程池中如果有一个线程出现异常怎么办?

      首先,必须使用互斥锁将你的操作在锁保护范围内;其次,就是使用try…catch进行异常捕获,一旦捕获异常就执行回滚操作 ;最后,只要保证同一时刻只有一个线程执行相应的操作并且执行完成后再释放锁,就能保证操作的数据一致性。

    参考:https://www.cnblogs.com/cheng07045406/p/3273466.html
    https://blog.csdn.net/K346K346/article/details/49538975
    https://blog.csdn.net/tuantuanls/article/details/41205739
    https://blog.csdn.net/lyztyycode/article/details/78648798
    https://blog.csdn.net/sayhello_world/article/details/72829329
    https://blog.csdn.net/rank_d/article/details/52253868
    https://www.cnblogs.com/alwayswangzi/p/7138154.html
    https://www.cnblogs.com/yusenwu/p/4685340.html
    https://blog.csdn.net/u011519624/article/details/69263460
    https://blog.csdn.net/fullstack/article/details/23712813

    展开全文
  • 的概念由于服务器的硬件资源“充裕”,那么提高服务器性能的一个很直接的方法就是以空间换时间,即“浪费”服务器的硬件资源,以换取其运行效率。这就是的概念。是一组资源的集合,这组资源在服务器启动之初就...

    池的概念

    由于服务器的硬件资源“充裕”,那么提高服务器性能的一个很直接的方法就是以空间换时间,即“浪费”服务器的硬件资源,以换取其运行效率。这就是池的概念。池是一组资源的集合,这组资源在服务器启动之初就完全被创建并初始化,这称为静态资源分配。当服务器进入正式运行阶段,即开始处理客户请求的时候,如果它需要相关的资源,就可以直接从池中获取,无需动态分配。很显然,直接从池中取得所需资源比动态分配资源的速度要快得多,因为分配系统资源的系统调用都是很耗时的。当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用来释放资源。从最终效果来看,池相当于服务器管理系统资源的应用设施,它避免了服务器对内核的频繁访问。

    池可以分为多种,常见的有内存池、进程池、线程池和连接池。

    内存池

    内存池是一种内存分配方式。通常我们习惯直接使用new、malloc等系统调用申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

    内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。

    进程池和线程池

    进程池和线程池相似,所以这里我们以进程池为例进行介绍。如没有特殊声明,下面对进程池的描述也适用于线程池。

    进程池是由服务器预先创建的一组子进程,这些子进程的数目在 3~10 个之间(当然这只是典型情况)。线程池中的线程数量应该和 CPU 数量差不多。

    进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级、 PGID 等。

    当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。至于主进程选择哪个子进程来为新任务服务,则有两种方法:

    1)主进程使用某种算法来主动选择子进程。最简单、最常用的算法是随机算法和 Round Robin (轮流算法)。

    2)主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。

    当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实现所有的进程间通信。在父线程和子线程之间传递数据就要简单得多,因为我们可以把这些数据定义为全局,那么它们本身就是被所有线程共享的。

    线程池主要用于:

    1)需要大量的线程来完成任务,且完成任务的时间比较短。 比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

    2)对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

    3)接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用

    展开全文
  • 连接(数据库连接、Redis连接池和HTTP连接等)通过复用TCP连接来减少创建释放连接的时间。线程池通过复用线程提升性能。简单来说,池化技术就是通过复用来提升性能。 线程、内存、数据库的连接对象都是资源...

    池化技术常见应用

    在系统开发过程中,我们经常会用到池化技术来减少系统消耗,提升系统性能。对象池通过复用对象来减少创建对象、垃圾回收的开销;连接池(数据库连接池、Redis连接池和HTTP连接池等)通过复用TCP连接来减少创建和释放连接的时间。线程池通过复用线程提升性能。简单来说,池化技术就是通过复用来提升性能。

    线程、内存、数据库的连接对象都是资源,在程序中,当你创建一个线程或者在堆上申请一块内存的时候都涉及到很多的系统调用,也是非常消耗CPU的。如果你的程序需要很多类似的工作线程或者需要频繁地申请释放小块内存,在没有对这方面进行优化的情况下,这部分代码很可能会成为影响你整个程序性能的瓶颈

    常见的池化技术的使用有:线程池、内存池、数据库连接池、HttpClient 连接池等,下面分别来看。

    1.线程池

    线程池的原理很简单,类似于操作系统中的缓冲区的概念。线程池中会先启动若干数量的线程,这些线程都处于睡眠状态。当客户端有一个新的请求时,就会唤醒线程池中的某一个睡眠的线程,让它来处理客户端的这个请求,当处理完这个请求之后,线程又处于睡眠的状态。

    线程池能很高地提升程序的性能。比如有一个省级数据大集中的银行网络中心,高峰期每秒的客户端请求并发数超过100,如果为每个客户端请求创建一个新的线程的话,那耗费的 CPU 时间和内存都是十分惊人的,如果采用一个拥有 200 个线程的线程池,那将会节约大量的系统资源,使得更多的 CPU 时间和内存用来处理实际的商业应用,而不是频繁的线程创建和销毁。

    2.内存池

    如何更好地管理应用程序内存的使用,同时提高内存使用的频率,这时值得每一个开发人员深思的问题。内存池(Memory Pool)就提供了一个比较可行的解决方案。

    内存池在创建的过程中,会预先分配足够大的内存,形成一个初步的内存池。然后每次用户请求内存的时候,就会返回内存池中的一块空闲的内存,并将这块内存的标志置为已使用。当内存使用完毕释放内存的时候,也不是真正地调用 free 或 delete 的过程,而是把内存放回内存池的过程,且放回的过程要把标志置为空闲。最后,应用程序结束就会将内存池销毁,将内存池中的每一块内存释放。

    3.数据库连接池

    数据库连接池的基本思想是在系统初始化的时候将数据库连接作为对象存储在内存中,当用户需要访问数据库的时候,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。在使用完毕后,用户也不是将连接关闭,而是将连接放回到连接池中,以供下一个请求访问使用,而这些连接的建立、断开都是由连接池自身来管理的。

    同时,还可以设置连接池的参数来控制连接池中的初始连接数、连接的上下限数和每个连接的最大使用次数、最大空闲时间等。当然,也可以通过连接池自身的管理机制来监视连接的数量、使用情况等。

     

    4.HttpClient 连接池

    HttpClient 我们经常用来进行 HTTP 服务访问。我们的项目中会有一个获取任务执行状态的功能使用 HttpClient,一秒钟请求一次,经常会出现 Conection Reset 异常。经过分析发现,问题是出在 HttpClient 的每次请求都会新建一个连接,当创建连接的频率比关闭连接的频率大的时候,就会导致系统中产生大量处于 TIME_CLOSED 状态的连接,这个时候使用连接池复用连接就能解决这个问题。

    展开全文
  • 采用boost内存数据库技术和线程池技术开发的内存池技术,支持内存回收,碎片合并
  • linux线程池内存池

    2011-11-07 21:19:54
            ...Linux下通用线程池的创建与使用 ...本文给出了一个通用的线程池框架,该框架将与...另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量。文章的最后,我们给出一个简单...

    http://blog.csdn.net/tingya/archive/2004/12/23/226614.aspx

     

     

     

     

     

    Linux下通用线程池的创建与使用
    本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关。另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量。文章的最后,我们给出一个简单示例程序,通过该示例程序,我们会发现,通过该线程池框架执行多线程任务是多么的简单。
     
    为什么需要线程池
    目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。
    传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。
    我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。
    T1:线程创建时间
    T2:线程执行时间,包括线程的同步等时间
    T3:线程销毁时间
    那么我们可以看出,线程本身的开销所占的比例为(T1+T3) / (T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%-50%左右。如果任务执行时间很频繁的话,这笔开销将是不可忽略的。
    除此之外,线程池能够减少创建的线程个数。 通常线程池所允许的并发线程是有上界的,如果同时需要并发的线程数超过上界,那么一部分线程将会等待。而传统方案中,如果同时请求数目为2000,那么最坏情况下,系统可能需要产生2000个线程。尽管这不是一个很大的数目,但是也有部分机器可能达不到这种要求。
    因此线程池的出现正是着眼于减少线程池本身带来的开销。 线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。
    基于这种预创建技术, 线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小,不过我们另外可能需要考虑进去线程之间同步所带来的开销。
     
    构建线程池框架
    一般线程池都必须具备下面几个组成部分:
    线程池管理器:用于创建并管理线程池
    工作线程: 线程池中实际执行的线程
    任务接口: 尽管线程池大多数情况下是用来支持网络服务器,但是我们将线程执行的任务抽象出来,形成任务接口,从而是的线程池与具体的任务无关。
    任务队列:线程池的概念具体到实现则可能是队列,链表之类的数据结构,其中保存执行线程。
    我们实现的通用线程池框架由五个重要部分组成CThreadManage,CThreadPool,CThread,CJob,CWorkerThread,除此之外框架中还包括线程同步使用的类CThreadMutex和CCondition。
    CJob是所有的任务的基类,其提供一个接口Run,所有的任务类都必须从该类继承,同时实现Run方法。该方法中实现具体的任务逻辑。
    CThread是Linux中线程的包装,其封装了Linux线程最经常使用的属性和方法,它也是一个抽象类,是所有线程类的基类,具有一个接口Run。
    CWorkerThread是实际被调度和执行的线程类,其从CThread继承而来,实现了CThread中的Run方法。
    CThreadPool是线程池类,其负责保存线程,释放线程以及调度线程。
    CThreadManage是线程池与用户的直接接口,其屏蔽了内部的具体实现。
    CThreadMutex用于线程之间的互斥。
    CCondition则是条件变量的封装,用于线程之间的同步。
    它们的类的继承关系如下图所示:
     
    线程池的时序很简单,如下图所示。CThreadManage直接跟客户端打交道,其接受需要创建的线程初始个数,并接受客户端提交的任务。这儿的任务是具体的非抽象的任务。CThreadManage的内部实际上调用的都是CThreadPool的相关操作。CThreadPool创建具体的线程,并把客户端提交的任务分发给CWorkerThread,CWorkerThread实际执行具体的任务。
     

     

    展开全文
  • 池化技术 - 简单点来说,就是提前保存大量的资源,以备不时之需,O(∩_∩)O,对于线程,内存,oracle的连接对象等等,这些都是资源,程序中当你创建一个线程或者在堆上申请一块内存时,都...池化技术主要有线程池,内
  • 一、池化技术-简单点来说,就是提前保存大量的资源,以备不时之需。 对于线程,内存,oracle的连接对象等等,这些都是资源,程序中当你创建一个线程或者在堆上申请一块内存时,都...池化技术主要有线程池内存池...
  • 池化技术应用广泛,如内存池线程池,连接池等等。内存池相关的内容,建议看看Apache、Nginx等开源web服务器的内存池实现。 由于在实际应用当做,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要...
  • 原理 : 线程池、连接池、内存池 https://blog.csdn.net/Fly_as_tadpole/article/details/81053630 前言 一、池化技术-简单点来说,就是提前保存...
  • 线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去中拿线程即可,节 省了开辟子线程的时间,提高的代码执行效率。 线程池的优点: 第一:降低资源消耗。通过重复利用已创建的...
  • 一、池化技术 ...对于线程,内存,oracle的连接对象等等,这些都是资源,程序中当你创建一个线程或者在堆上申请一块内存时,都涉及到很多系统调用,也是非常消耗CPU的,如果你的程序需要很多类似的...
  • 7 内存池线程池、进程池及实现 池 由于服务器的硬件资源“充裕”,那么提高服务器性能的一个很直接的方法就是以空间换时间,即**“浪费”服务器的硬件资源,以换取其运行效率**。这就是池的概念。 池是一组资源的...
  • 的概念 ...由于服务器的硬件资源“充裕”,那么提高服务器性能的一个很直接的方法就是以空间换时间...是一组资源的集合,这组资源在服务器启动之初就完全被创建并初始化,这称为静态资源分配。当服务器进入正
  • 进程池,线程池内存池
  • 1、的概念  一般来说,服务器的硬件资源相对充裕,很多时候我们使用以空间换时间的方法来提高服务器的性能,不惜浪费更多的空间以换取服务器运行效率。具体做法是提前保存大量的资源,以备不时之需以及重复使用...
  • 线程池和连接

    2019-05-21 15:48:14
    一、 线程池的原理: 线程池,究竟是怎么一回事?其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下: 先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒...
  • tomcat 线程池和连接

    千次阅读 2013-10-16 16:57:19
    在介绍如何配置tomcat线程池和连接之前,先介绍一下线程池和连接的原理。 线程池的原理: 其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都...
  • 线程池和连接的区别

    万次阅读 2017-07-20 20:10:45
    一、 线程池的原理:  线程池,究竟是怎么一回事?其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下: 先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就...
  • 内存池 平常我们使用new、malloc在堆区申请一块内存,但由于每次申请的内存大小不一样就会产生很多内存碎片,造成不好管理与浪费的情况。 内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况...
  • 线程池 1、流程 先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。 2、...
  • java 线程池和连接

    2012-07-18 01:52:29
    其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个...
  • 线程池和连接 线程池的原理: 来看一下线程池究竟是怎么一回事?其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 37,483
精华内容 14,993
关键字:

线程池和内存池技术