三大池式组件实现

文章目录

  • 线程池:
    • 项目背景:
    • 线程池的实现原理:
      • 线程池的结构:
      • 线程数组:
      • 任务队列:
      • 任务队列和线程数组的不同:
      • 管理者线程:
    • 整体代码:
      • thread_pool.h
      • thread_pool.c
  • MySQL的连接池
    • 关键技术点:
    • 项目源码:
    • 项目背景:
    • 连接池功能点介绍:
    • 功能实现设计:
      • 代码结构:
      • 实现要点:
    • 准备工作:
      • 创建数据库和表:
      • 创建配置文件mysql.ini:
      • 执行日志log.h:
      • 封装与`MySQL Server`交互操作:
        • Connection.h
        • Connection.cpp:
      • 测试与mysql的连接并发送插入语句:
      • windows下visual studio遇到的问题:
    • 连接池的实现:
      • ConnectionPool.h:
      • ConnectionPool.cpp:
    • 连接池压力测试:
      • 单线程测试:
        • 未使用线程池:
        • 使用线程池:
      • 多线程测试:
        • 未使用线程池:
        • 使用线程池:
      • 测试结果:
        • 通过插入操作,衡量使用连接池对性能的影响:
        • 结论:
  • 内存池的实现:
    • 为何需要内存池?
    • 分析SGI STL二级空间配置器内存池实现原理:
      • 空间配置器的相关定义:
      • 二级空间配置器源码分析:
        • 重要的类型和变量定义:
        • 重要的辅助接口函数:
        • 内存池管理函数:
      • SGI STL二级空间配置器内存池的优点:
      • 自实现stl_alloc功能:
          • __malloc_alloc_template.h
          • MyAllocator.h
          • main.cpp
    • nginx的内存池实现原理:
      • nginx内存池源码分析:
        • 内存池的关系图:
        • 重要类型定义:
        • 重要函数接口:
        • ngx_create_pool创建内存池:
        • ngx_palloc内存分配函数(支持内存对齐):
          • ngx_palloc_small小块内存分配
          • ngx_palloc_large大块内存分配:
        • ngx_pfree内存释放:
        • ngx_reset_pool内存池重置:
        • ngx_destroy_pool销毁内存池:
      • 测试用例:
        • 环境配置:
        • test_ngx_memoryPool.c
        • 编译连接命令:
      • 自实现nginx_palloc的功能:
        • ngx_memoryPool.h
        • ngx_memoryPool.cpp
        • main.cpp

线程池:

项目背景:

线程池是一种多线程处理形式,大多用于高并发服务器上,为了有效的利用高并发服务器上的线程资源。

网络编程中,线程与进程处理各项分支子功能,通常是:接收消息 ==> 消息分类 ==> 线程创建 ==> 传递消息到子线程 ==> 线程分离 ==> 在子线程中执行任务 ==> 任务结束退出。

当出现大量消息频繁请求服务器时,频繁的创建与销毁线程会带来极大的开销,这会造成内存资源的极度浪费。

线程池,则允许一个线程多次复用,且每次复用的线程内部的消息处理可以不相同,从而能节省创建与销毁的开销。

线程池的实现原理:

三大池式组件实现_第1张图片

线程池的结构:

  1. 线程池状态信息:描述当前线程池的基本信息,如是否开启shutdown、最小线程数min_thr_num、最大线程数max_thr_num、存活线程数live_thr_num、忙线程数busy_thr_num、待销毁线程数wait_exit_thr_num等;
  2. 线程数组、管理者线程:各自的线程ID分别存放在threadsadjust_tid中;
  3. (环形)任务队列信息:描述当前任务队列基本信息,如任务队列的首地址、最大任务数、实际任务数、队列头/尾的下标、队列不为满条件变量、队列不为空条件变量等;
  4. 多线程互斥锁:整个线程池的互斥锁lock、忙线程数变量锁thread_counter。保证同一时间点上,只有一个线程在任务队列中取任务并修改任务队列、线程池的信息;
  5. 函数指针:在打包消息阶段,将分类后的消息处理函数放在(*function);
  6. void*类型参数:用于传递消息处理函数需要的信息。
/* 描述线程池相关信息 */
struct threadpool_t {
   pthread_mutex_t lock;                 /* 锁住整个结构体 */
   pthread_mutex_t thread_counter;       /* 记录忙线程数busy_thr_num的锁 */
   pthread_cond_t  queue_not_full;       /* 当任务队列满时,添加任务的线程阻塞,等待该条件变量满足 */
   pthread_cond_t  queue_not_empty;      /* 任务队列不为空时,通知等待任务的线程 */

   pthread_t* threads;                   /* 存放线程池中,每个线程的tid数组 */
   pthread_t adjust_tid;                 /* 管理者线程的tid */
   threadpool_task_t* task_queue;        /* 任务队列(数组的首地址) */

   /* 线程池信息 */
   int min_thr_num;                      /* 线程池中,最小线程数 */
   int max_thr_num;                      /* 线程池中,最大线程数 */
   int live_thr_num;                     /* 线程池中,存活的线程数 */
   int busy_thr_num;                     /* 忙线程(正在工作的线程)数 */
   int wait_exit_thr_num;                /* 需要销毁的线程数 */

   /* 任务队列信息 */
   int queue_front;                      /* 队头下标 */
   int queue_rear;                       /* 队尾下标 */
   int queue_size;                       /* 队列中,实际的任务数 */
   int queue_max_size;                   /* 队列能容纳的最大任务数 */

   /* 状态 */
   int shutdown;                         /* 标志位,线程池的使用状态: true/false */
};

线程数组:

  1. 本质上是线程池初始化时,申请的的一段存放一堆线程tid的堆空间。
  2. 这段空间中包含了正在工作的线程空闲线程(等待工作的线程)、等待被销毁的线程没有初始化的剩余线程空间(管理者线程会“定时”进行“扩容”和“瘦身”)。

任务队列:

  1. 存在形式与线程数组相似,线程池初始化时根据传入的最大任务数开辟空间。
  2. 当服务器前方后请求到来后,分类并打包消息成为任务,将任务放入任务队列并通知空闲线程来取。
/* 任务 */
struct threadpool_task_t {
   void*(*function)(void*);
   void* arg;
};

任务队列和线程数组的不同:

  • 任务队列中,有明显的先后顺序,先进先出FIFO(用环形队列来模拟);
  • 线程数组中,各个线程是竞争关系去拿到互斥锁争取任务

管理者线程:

  1. 本质上,就是一个单独的线程定时检查,来对线程池进行**“扩容”和“瘦身”**;
  2. 维护线程池平衡的算法,即“扩容”和“瘦身”:
    • “扩容”算法(在剩余空间中,创建新的线程): 存活的线程数 < 最大线程数 && 实际任务数量 >= 最小线程数
    • “瘦身”算法(销毁多余的空闲线程):存活的线程数 > 最小线程数 && 忙线程x2 < 存活线程

整体代码:

thread_pool.h

#ifndef __THREADPOOL_H_
#define __THREADPOOL_H_
#include 

/* 任务 */
struct threadpool_task {
   void*(*function)(void*);
   void* arg;
};
typedef struct threadpool_task threadpool_task_t;

/* 描述线程池相关信息 */
struct threadpool {
   pthread_mutex_t lock;                 /* 锁住整个结构体 */
   pthread_mutex_t thread_counter;       /* 记录忙线程数busy_thr_num的锁 */
   pthread_cond_t  queue_not_full;       /* 当任务队列满时,添加任务的线程阻塞,等待该条件变量满足 */
   pthread_cond_t  queue_not_empty;      /* 任务队列不为空时,通知等待任务的线程 */

   pthread_t* threads;                   /* 存放线程池中,每个线程的tid数组 */
   pthread_t admin_tid;                 /* 管理者线程的tid */
   threadpool_task_t* task_queue;        /* 任务队列(数组的首地址) */

   /* 线程池信息 */
   int min_thr_num;                      /* 线程池中,最小线程数 */
   int max_thr_num;                      /* 线程池中,最大线程数 */
   int live_thr_num;                     /* 线程池中,存活的线程数 */
   int busy_thr_num;                     /* 忙线程(正在工作的线程)数 */
   int wait_exit_thr_num;                /* 需要销毁的线程数 */

   /* 任务队列信息 */
   int queue_front;                      /* 队头下标 */
   int queue_rear;                       /* 队尾下标 */
   int queue_size;                       /* 队列中,实际的任务数 */
   int queue_max_size;                   /* 队列能容纳的最大任务数 */

   /* 状态 */
   int shutdown;                         /* 标志位,线程池的使用状态: true/false */
}; 
typedef struct threadpool thread_pool_t;

/* 创建线程池 */
threadpool_t* threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size);

/* 释放线程池 */
int threadpool_free(threadpool_t* pool);

/* 销毁线程池 */
int threadpool_destroy(threadpool_t* pool);

/* 管理线程 */
void* admin_thread(void* threadpool);

/* 线程是否存在 */
//int is_thread_alive(pthread_t tid);

/* 工作线程 */
void* threadpool_thread(void* threadpool);

/* 向线程池的任务队列中添加一个任务 */
int threadpool_add_task(threadpool_t* pool, void*(*function)(void*arg), void* arg);

#endif

thread_pool.c

#include 
#include 
#include 
#include 
#include  
#include 
#include "threadpool.h"

#define DEFAULT_TIME 1             /* 默认时间10s */
#define MIN_WAIT_TASK_NUM 10       /* 当任务数超过了它,就该添加新线程了 */
#define DEFAULT_THREAD_NUM 10      /* 每次创建或销毁的线程个数 */
#define true 1
#define false 0


/* 创建线程池 */
threadpool_t* threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)
{ 
   threadpool_t* pool = NULL;
   do
   {
      if ((pool = (threadpool_t*)malloc(sizeof(threadpool_t))) == NULL)
      {
        printf("malloc threadpool false;\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_front = 0;
      pool->queue_rear = 0;
      pool->queue_size = 0;
      pool->queue_max_size = queue_max_size;
      pool->shutdown = false;

      /* 根据最大线程数,给工作线程数组开空间,并清0 */
      pool->threads = (pthread_t*)malloc(sizeof(pthread_t)*max_thr_num);
      if (pool->threads == NULL)
      {
         printf("malloc threads false;\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 false;\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 false;\n");
         break;
      }

      /* 创建min_thr_num个工作线程 */
      for (int i=0; i < min_thr_num; ++i)
      { 
         pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void*)pool);  // pool指向当前线程池 
         printf("start thread 0x%x...\n", (unsigned int)pool->threads[i]);
      }
      /* 创建管理者线程 */
      pthread_create(&(pool->admin_tid), NULL, admin_thread, (void*)pool);

      return pool;
   } while(0);

   /* 当do-while中,某一个步骤出错了,即失败时释放pool的空间并返回NULL */
   threadpool_free(pool);
   return NULL;
}

/* 释放线程池 */
int threadpool_free(threadpool_t *pool)
{
   if (pool == NULL)
   {
     return -1;
   }

   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);
   pool = NULL;

   return 0;
}

/* 销毁线程池 */
int threadpool_destroy(threadpool_t *pool)
{ 
   if (pool == NULL)
   {
      return -1;
   }
   pool->shutdown = true;

   /* 1、销毁管理者线程 */
   pthread_join(pool->admin_tid, NULL);

   /* 2、通知所有线程(在自己领任务的过程中)去自杀 */
   for (int i = 0; i < pool->live_thr_num; ++i)
   {
      // 本质上是执行了threadpool_thread函数中`if (pool->wait_exit_thr_num > 0)`的条件分支
      pthread_cond_broadcast(&(pool->queue_not_empty));
   }

   /* 等待各个线程结束: 先是pthread_exit()执行,然后等待其结束 */
   for (int i = 0; i < pool->live_thr_num; ++i)
   {
      pthread_join(pool->threads[i], NULL);
   }

   threadpool_free(pool);
   return 0;
}

/* 线程是否存活 */
int is_thread_alive(pthread_t tid)
{
   int kill_rc = pthread_kill(tid, 0);     // 发送0号信号,测试是否存活
   if (kill_rc == ESRCH)                   // 线程不存在
   {
      return false;
   }
   return true;
}

/* 管理线程 */
void* admin_thread(void* threadpool)
{ 
   threadpool_t* pool = (threadpool_t*)threadpool;
   while (!pool->shutdown)
   { 
      sleep(DEFAULT_TIME);                             // “定时”管理线程池

      /* 获取当前线程池中,任务数queue_size、存活的线程数live_thr_num、忙线程数busy_thr_num */ 
      pthread_mutex_lock(&(pool->lock));               // 对pool进行加锁
      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));     // 对记录忙线程数busy_thr_num的锁,进行加锁
      int busy_thr_num = pool->busy_thr_num;           // 忙线程数
      pthread_mutex_unlock(&(pool->thread_counter));
 
      /* 创建新线程算法: 实际任务数量 >= 最小正在等待的任务数量、 存活的线程数 < 最大线程数 */
      if (queue_size >= MIN_WAIT_TASK_NUM && live_thr_num < pool->max_thr_num)
      { 
         pthread_mutex_lock(&(pool->lock));
 
         /* 一次增加 DEFAULT_THREAD_NUM 个线程 */
         for (int i = 0, add = 0; i < pool->max_thr_num && add < DEFAULT_THREAD_NUM && pool->live_thr_num < pool->max_thr_num; ++i, ++add)
         {
            if (pool->threads[i] == 0 || !is_thread_alive(pool->threads[i]))
            {
               pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void*)pool); 
               pool->live_thr_num++; 
            }
         }

         pthread_mutex_unlock(&(pool->lock));
      }

      /* 销毁多余的空闲线程算法: 忙线程x2 < 存活线程、 存活的线程数 > 最小线程数 */
      if ((busy_thr_num*2) < live_thr_num  &&  live_thr_num > pool->min_thr_num)
      { 
         /* 一次随机销毁 DEFAULT_THREAD_NUM 个线程 */
         pthread_mutex_lock(&(pool->lock));
         pool->wait_exit_thr_num = DEFAULT_THREAD_NUM;
         pthread_mutex_unlock(&(pool->lock));

         for (int i = 0; i < DEFAULT_THREAD_NUM; ++i)
         {
            // 通知正在处于空闲的线程,自杀: 本质上是执行了threadpool_thread函数中`if (pool->wait_exit_thr_num > 0)`的条件分支
            pthread_cond_signal(&(pool->queue_not_empty));
         }
      }
   }

   return NULL;
}

/* 工作线程 */
void* threadpool_thread(void* threadpool)
{
   threadpool_t* pool = (threadpool_t*)threadpool;
   threadpool_task_t task;

   while(true)
   {
      /* 用来锁住pool */
      pthread_mutex_lock(&(pool->lock));

      /* 刚创建的线程,会阻塞等待任务队列中有任务后,唤醒接收任务 */
      while ((!pool->shutdown) && (pool->queue_size == 0))
      { 
         printf("thread 0x%x is waiting \n", (unsigned int)pthread_self());
         pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));

         // 判断是否需要清除线程,如果要结束的线程个数大于0,则结束线程
         if (pool->wait_exit_thr_num > 0)
         {
            pool->wait_exit_thr_num--;
            // 判断线程池中,线程数是否大于最小线程数,是则结束当前线程
            if (pool->live_thr_num > pool->min_thr_num)
            {
               printf("thread 0x%x is exiting \n", (unsigned int)pthread_self());
               pool->live_thr_num--;
               pthread_mutex_unlock(&(pool->lock));
               pthread_exit(NULL);                         // 结束当前线程
            }
         }
      }

      /* 如果指定shutdown==true,则关闭线程池里的每个线程,自行退出处理,即销毁线程 */
      if (pool->shutdown)               // 线程池已关闭
      { 
         pthread_mutex_unlock(&(pool->lock));
         printf("thread 0x%x is exiting \n", (unsigned int)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_size--;

      /* 通知可以添加新任务 */
      pthread_cond_broadcast(&(pool->queue_not_full));

      /* 任务取出后,立即释放线程池pool的锁 */
      pthread_mutex_unlock(&(pool->lock));

      /* 执行刚才取出的任务 */
      printf("thread 0x%x start working \n", (unsigned int)pthread_self());
      pthread_mutex_lock(&(pool->thread_counter));            // 忙线程数变量锁
      pool->busy_thr_num++;                                   // 忙线程数+1
      pthread_mutex_unlock(&(pool->thread_counter));

      (*(task.function))(task.arg);                           // 执行回调函数任务

      /* 任务结束处理 */
      printf("thread 0x%x end working \n", (unsigned int)pthread_self());
      pthread_mutex_lock(&(pool->thread_counter));
      pool->busy_thr_num--;                                   // 忙线程数-1
      pthread_mutex_unlock(&(pool->thread_counter));
   }

   pthread_exit(NULL);
}

/* 向线程池的任务队列中添加一个任务(设置回调函数和参数) */
int threadpool_add_task(threadpool_t* pool, void*(*function)(void *arg), void* arg)
{
   /* 对pool进行加锁 */
   pthread_mutex_lock(&(pool->lock));

   /* 队列已满,调用wait阻塞,等待条件变量queue_not_full满足 */
   while ((!pool->shutdown) && (pool->queue_size == pool->queue_max_size))
   {
      pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
   }

   /* 如果线程池处于关闭状态 */
   if (pool->shutdown)
   {
      pthread_mutex_unlock(&(pool->lock));
      return -1;
   }

   /* 清空工作线程的回调函数的参数arg */
   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_size++;

   /* 添加完任务后,队列就不为空了,唤醒线程池中的一个线程 */
   pthread_cond_signal(&(pool->queue_not_empty));  // 本质上,执行的是threadpool_thread函数中后半段:取出并占用一个线程来执行任务,任务结束后恢复

   pthread_mutex_unlock(&(pool->lock));

   return 0;
}

/* 模拟实际的任务 */
void* process(void* arg)
{
   printf("thread 0x%x working on task %d\n", (unsigned int)pthread_self(), (int)arg);
   sleep(1);   // 模拟处理任务,所需要的时间
   printf("task %d is end\n", (int)arg);

   return NULL;
}

int main(void) 
{
   // 创建线程池:池中最少3个线程,最多100个线程,队列能容纳的最多的任务数
   threadpool_t* thp = threadpool_create(3, 100, 100);
   printf("has inited the thread pool\n");

   int nums[20];
   for (int i = 0; i < 20; ++i)
   {
      nums[i] = i;
      printf("add task %d\n", i);

      // 向线程池中,添加任务
      threadpool_add_task(thp, process, (void*)&nums[i]);
   }

   // 等待子线程完成任务
   sleep(10);
   threadpool_destroy(thp);

   return 0;
}

MySQL的连接池

关键技术点:

MySQL数据库编程、单例模式、C++11多线程编程、线程互斥、线程同步通信和unique_lock、基于CAS的原子整形、智能指针shared_ptrlambda表达式、生产者-消费者线程模型

项目源码:

源码及详细说明

项目背景:

在C++开发中,和数据库交互,通常需要以下几个过程:

  1. TCP三次握手
  2. mysql_connect建立连接
  3. 执行SQL语句
  4. mysql_close释放连接
  5. TCP四次挥手

为了提高MySQL数据库(基于C/S设计)的访问瓶颈,除了在服务器端增加缓存服务器(缓存常用的数据)外(如redis),还可以增加连接池(减小交互过程中,步骤1、2、4、5的时间消耗,以达到连接复用的目的),来提高MySQL Server的访问效率。

在高并发情况下,大量的TCP三次握手MySQL Server连接认证、MySQL Server关闭连接回收资源、TCP四次挥手,所耗费的性能时间也是很明显的,增加连接池就是为了减少这一部分的性能损耗。

本项目就是为了在C++项目中,提升MySQL Server的访问效率,实现基于C++代码的数据库连接池模块。

连接池功能点介绍:

连接池一般包含了数据库连接所用的ip地址、port端口号、root用户名和password密码以及其它的性能参数,例如initSize初始连接量,maxSize最大连接量,maxIdleTime最大空闲时间、connectionTimeout连接超时时间等,该项目是基于C++语言实现的连接池,主要也是实现以上几个所有连接池都支持的通用基础功能。

  1. 初始连接量(initSize:连接池初始化时会和MySQL Server创建initSizeconnection连接。当应用发起MySQL访问时,直接从连接池中获取一个可用的连接即可,使用完成后把当前connection再归还到连接池当中。
  2. 最大连接量(maxSize:当并发访问MySQL Server的请求增多,初始连接量已经不够时,此时需要根据新的请求数量去创建更多的连接给应用去使用,但新创建的连接数量上限是maxSize,不能无限制的创建连接(因为每一个连接都会占用一个socket资源,一般连接池和服务器程序部署在一台主机上,如果连接池占用过多的socket资源,那么服务器就不能接收太多的客户端请求了)。
  3. 最大空闲时间(maxIdleTime:如果在指定的maxIdleTime里面,新增加的连接没有被再次使用过,那么新增加的这些连接资源就要被回收掉,但需要始终保持initSize个初始连接量。
  4. 连接超时时间(connectionTimeout:当MySQL的并发请求量过大,使得连接池中的连接数量已经到达maxSize(没有空闲的连接可使用),故此时应用无法从连接池获取连接,则通过阻塞的方式获取连接。但如果超过connectionTimeout时间,则获取连接失败,无法访问数据库。

该项目主要实现上述的连接池四大功能,可为连接池扩展更多的功能…

功能实现设计:

代码结构:

平台:windows visual studio 2017 x64

ConnectionPool.cppConnectionPool.h:连接池代码实现

Connection.cppConnection.h:数据库操作代码、增/删/改/查代码实现

实现要点:

连接的生产和消费,采用“生产者-消费者线程模型”来设计,使用了线程间的同步通信机制“条件变量和互斥锁”。

三大池式组件实现_第2张图片

  1. 连接池只需要一个实例,所以ConnectionPool以单例模式进行设计。从ConnectionPool中可以获取和MySQL的连接Connection

  2. 空闲连接Connection全部维护在一个线程安全的Connection队列中,使用线程互斥锁保证队列的线程安全

  3. 管理连接池,对其进行**“扩容”和“瘦身”**。

    • (生产者线程)扩容:如果Connection队列空闲连接为空时,还需要再获取连接,则进行“扩容”,但上限数量是maxSize
    • (管理者线程 “定时任务”)瘦身:如果Connection队列空闲连接时间超过maxIdleTime就要被释放掉,但最少保留初始的initSize个连接;
  4. 如果Connection队列空闲连接为空,且连接的数量已达上限maxSize,则等待connectionTimeout时间。等待期间,仍无法从Connection队列获取空闲连接,则连接失败,无法访问数据库。

  5. 用户获取的连接用shared_ptr(_Ux* _Px, _Dx _deleter)智能指针来管理,用lambda表达式定制连接释放的功能(不是真正释放连接,而是把连接归还到连接池中)。

准备工作:

创建数据库和表:

mysql -u root -p 
passwd:******

create datebase chat;
use chat;
create table stuinfo(id int, name varchar(20), age int, sex varchar(10));
alter table stuinfo modify id int primary key auto_increment;  # 设置id为该stuinfo表的“主键且自增长”列

创建配置文件mysql.ini:

// 在项目中,创建mysql.ini文件
# 数据库连接池的配置文件,连接的是本地主机localhost:3306的chat数据库
ip=127.0.0.1    
port=3306       
username=root
password=******
dbname=chat

initSize=10
maxSize=1024

maxIdleTime=60         # 最大空闲时间s

connectionTimeOut=100  # 连接超时时间ms

执行日志log.h:

log.h文件中,定义了一个宏函数LOG(str),用来打印执行过程中的日志。

#pragma once

#define LOG(str) \
	cout << __FILE__ << ":" << __LINE__ << " " << \
	__TIMESTAMP__ << " : " << str << endl;

封装与MySQL Server交互操作:

Connection.h

#pragma once
#include 
#include 
#include 
using namespace std;

/* 实现MySQL数据库的操作 */
class Connection
{
public:
	// 初始化数据库连接
	Connection();
	// 释放数据库连接资源
	~Connection();

	// 连接数据库
	bool connect(string ip, unsigned short port, string user, string password, string dbname);

	// 更新操作 insert、delete、update
	bool update(string sql);
	// 查询操作 select
	MYSQL_RES* query(string sql);
    void PrintQueryResult(MYSQL_RES* results);

	// 刷新一下连接的起始的空闲时间点
	void refreshAliveTime() { _alivetime = clock(); }

	// 返回存活的时间
	clock_t getAliveeTime()const { return clock() - _alivetime; }
private:
	MYSQL* _conn;        // 与MySQL Server的一条连接
	clock_t _alivetime;  // 记录进入空闲状态后的起始存活时间
};

Connection.cpp:

#include "pch.h"
#include "log.h"
#include "Connection.h"
#include 
using namespace std;

Connection::Connection()
{
	/* 
		初始化数据库连接:
		MYSQL* mysql_init(MYSQL *mysql);
	*/
	_conn = mysql_init(nullptr);
}

Connection::~Connection()
{
	/*
		释放数据库连接资源:
		void mysql_close(MYSQL* sock);
	*/
	if (_conn != nullptr)
	{
		mysql_close(_conn);
	}
}

bool Connection::connect(string ip, unsigned short port, string username, string password, string dbname)
{
	/*
		连接数据库:
		MYSQL* mysql_real_connect(MYSQL *mysql
		, const char *host, const char *user, const char *passwd, const char *db
		, unsigned int port, const char* unix_socket, unsigned long clientflag);
	*/
	MYSQL* p = mysql_real_connect(_conn, ip.c_str(), username.c_str(), password.c_str(), dbname.c_str(), port, nullptr, 0);
	return p != nullptr;
}

/*
	操作mysql表:
	int mysql_query(MYSQL* mysql, const char* sql);
	Return Values:Zero for success,Nonzero if an error occurred.
*/
bool Connection::update(string sql)
{
	// 更新操作:insert、delete、update  
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("insert/delete/update failure:" + sql);
         // LOG_INFO << __FILE__ << ":" << __LINE__ << ":" << sql << "更新失败!";
		return false;
	}
	return true;
} 
MYSQL_RES* Connection::query(string sql)
{
	// 查询操作:select
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("query failure:" + sql);
         // LOG_INFO << __FILE__ << ":" << __LINE__ << ":" << sql << "查询失败!";
		return nullptr;
	}

    /*
    mysql_store_result():
    1. “本次查询的所有结果都缓存到客户端”,这样做的好处是可以随意的访问结果中的值,
    2. 可以使用mysql_data_seek() 和mysql_row_seek() 访问任意位置的数据或者行;同时由于数据是缓存到本地的,可以直接使用 mysql_num_rows() 来获取结果的行数。
    4. 不利的一方面在于会消耗内存来缓存这些结果(一般情况可以忽略)。
	
	mysql_use_result():
	1. 与 mysql_store_result() 最大的不同之处在于,mysql_use_result() 只在客户端 “缓存一行结果”,所以 mysql_use_result() 的调用速度会快一些,内存的需求也小。
	2. 不好的方面:无法随机访问(只能顺序访问,即一行一行的从服务器取回查询结果)、无法直接获得本次查询结果的行数(每当从服务器获取一条结果就增加一次行数,直到服务器没有数据可读,此时才能获得查询结果的行数)。 
    */
    
	/*
		mysql_store_result()函数:
		Retrieves a complete result set to the client, allocates a MYSQL_RES structure, and places the result into this structure.
		1、returns a null pointer if the statement did not return a result set.
		   for example, if it was an INSERT statement or if reading of the result set failed.
		2、You can check whether an error occurred by checking whether mysql_error() returns a nonempty string.
		   Return Values:A MYSQL_RES result structure with the results.NULL(0) if an error occurred.
	*/
	MYSQL_RES* res = mysql_store_result(_conn);
	if (!res) {
		printf("Couldn't get result from %s\n", mysql_error(_conn));
		return nullptr;
	}
	 
	return res;
} 

void Connection::PrintQueryResult(MYSQL_RES* results)
{
	/*
	my_ulonglong mysql_affected_rows(MYSQL* mysql)
	1、It returns the number of rows changed, deleted, or inserted by the last statement if it was an UPDATE, DELETE, or INSERT.
	2、For SELECT statements, returns the number of rows in the result set.
*/
	printf("number of dataline returned: %d\n", mysql_affected_rows(_conn));

	// 获取列数
	int j = mysql_num_fields(results);

	// 获取字段名
	/*
		MYSQL_FIELD* mysql_fetch_field(MYSQL_RES* result)
		1、Returns the definition of one column of a result set as a MYSQL_FIELD structure.
		2、Call this function repeatedly to retrieve information about all columns in the result set.
	*/
	char* str_field[32];
	for (int i = 0; i < j; ++i)
	{
		str_field[i] = mysql_fetch_field(results)->name;
		printf("%10s\t", str_field[i]);
	}
	printf("\n");

	// 打印查询结果
	/*
		MYSQL_ROW mysql_fetch_row(MYSQL_RES* result)
		Fetches the next row from the result set
	*/
	MYSQL_ROW column;
	while (column = mysql_fetch_row(results))
	{
		for (int i = 0; i < j; ++i)
		{
			printf("%10s\t", column[i]);
		}
		printf("\n");
	} 
} 

测试与mysql的连接并发送插入语句:

#include "pch.h"
#include 
using namespace std;
#include "Connection.h"
#include "CommonConnectionPool.h"

int test_mysql_connection()
{
	// sql statements:
	char sql[1024] = { 0 };
	sprintf(sql, "insert into stuinfo(name, age, sex) values('%s', %d, '%s')", "zhang san", 20, "male");

	// mysql server's connection:
	Connection conn;
	conn.connect("127.0.0.1", 3306, "root", "******", "chat");   // "******"填写登录密码
	
	// execute sql statements:
	conn.update(sql);
    
    // execute sql statements:
    strcpy(sql, "select * from stuinfo");
	MYSQL_RES* results = conn.query(sql);
	conn.PrintQueryResult(results);

	return 0;
}

windows下visual studio遇到的问题:

1、MySQL数据库编程直接采用oracle公司提供的MySQL C/C++客户端开发包,在VS上需要进行相应的头文件和库文件的配置,如下:

  • 右键项目 - “属性” - “配置属性” - C/C++ - 常规 - 附加包含目录,填写mysql.h头文件的路径
  • 右键项目 - “属性” - “配置属性” - 链接器 - 常规 - 附加库目录,填写libmysql.lib的路径
  • 右键项目 - “属性” - “配置属性” - 链接器 - 输入 - 附加依赖项,填写libmysql.lib库的名字
  • libmysql.dll动态链接库(Linux下后缀名是.so库)放在工程目录下

2、windows下,visual studio,未定义标识符 “SOCKET”。

/* 解决方法: */
// 1、在 mysql_com.h 文件中,include这两个文件
#include 
#include   
// 其中,定义了 windows 下网络编程所需的常量、结构体和函数。

// 2、在项目设置中添加以下库文件:"项目" - "属性" - "配置属性" - "链接器" - "输入" - "附加依赖项" - "Ws2_32.lib"
Ws2_32.lib
// 这个库文件包含了 Windows 套接字 API 所需的函数和符号。

3、出现fopen/strcpy/sprintf不安全提示:

Error --> 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.	
    
/*
解决方法:
使用编译器提供的忽略警告选项:在Visual Studio中,可在项目属性的“C/C++ - 预处理器”中,添加预处理器定义_CRT_SECURE_NO_WARNINGS,从而禁用此警告。
*/

连接池的实现:

ConnectionPool.h:

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#include "Connection.h"

/* 实现连接池功能模块 */
class ConnectionPool
{
public:
	// 提供静态成员函数,获取(单例模式下)连接池类对象实例
	static ConnectionPool* initConnectionPool();
	// 从连接池中,获取一个可用的空闲连接
	shared_ptr<Connection> getConnection();
private:
	// 初始化连接池,并创建生产者线程和管理者线程(定时扫描)
	ConnectionPool();

	// 从配置文件中加载配置项
	bool loadConfigFile(); 

	// 生产者线程:负责当 _connectionQue.empty() && _connectionCnt < _maxSize 时,生产新连接
	void produceConnectionTask();

	// 扫描超过maxIdleTime时间的空闲连接,进行对于的连接回收
	void scannerConnectionTask();

	string _ip; // mysql的ip地址
	unsigned short _port; // mysql的端口号 3306
	string _username;     // mysql登录用户名
	string _password;     // mysql登录密码
	string _dbname;       // 连接的数据库名称
	int _initSize;             // 连接池的初始连接量
	int _maxSize;              // 连接池的最大连接量
	int _maxIdleTime;          // 连接池最大空闲时间s
	int _connectionTimeout;    // 连接池获取连接的超时时间ms

	queue<Connection*> _connectionQue;  // 存储mysql连接的队列
	atomic_int _connectionCnt;          // 记录连接所创建的connection连接的总数量 
	mutex _queueMutex;          // 维护连接队列的线程安全互斥锁 
	condition_variable cv;      // 设置条件变量,用于生产线程、消费线程间同步通信
};

ConnectionPool.cpp:

深入理解c++智能指针:unique_ptr、weak_ptr和shared_ptr(解决交叉引用(即解决存在内存泄露的问题)、线程安全(线程入口函数的形参用weak_ptr,内部将weak_ptr通过lock()方法提升为shared_ptr来判断该块共享内存是否还存在)的问题)。

#include "ConnectionPool.h"
#include "log.h"

// 线程安全的懒汉单例函数接口
ConnectionPool* ConnectionPool::initConnectionPool()
{
	static ConnectionPool pool; // lock和unlock
	return &pool;
}

// 从配置文件中加载配置项
bool ConnectionPool::loadConfigFile()
{
	FILE* pf = fopen("mysql.ini", "r");
	if (pf == nullptr)
	{
		LOG("mysql.ini is not exist!");
		return false;
	}

	while (!feof(pf))
	{
		char line[1024] = { 0 };
		fgets(line, 1024, pf);
		string str = line;

		int idx = str.find('=', 0);
		if (idx == -1) // 无效的配置项
		{
			continue;
		}

		int endidx = str.find('\n', idx);
		string key = str.substr(0, idx);
		string value = str.substr(idx + 1, endidx - idx - 1);

		if (key == "ip"){
			_ip = value;
		} else if (key == "port"){
			_port = atoi(value.c_str());
		} else if (key == "username"){
			_username = value;
		} else if (key == "password") {
			_password = value;
		} else if (key == "dbname") {
			_dbname = value;
		} else if (key == "initSize") {
			_initSize = atoi(value.c_str());
		} else if (key == "maxSize") {
			_maxSize = atoi(value.c_str());
		} else if (key == "maxIdleTime") {
			_maxIdleTime = atoi(value.c_str());
		} else if (key == "connectionTimeOut") {
			_connectionTimeout = atoi(value.c_str());
		}
	}
	return true;
}

// 初始化连接池,并创建生产者线程和管理者线程(定时扫描回收)
ConnectionPool::ConnectionPool()
{
	// 从配置文件mysql.ini加载配置项
	if (!loadConfigFile())
	{
		return;
	}

	// 创建初始数量的connection连接
	for (int i = 0; i < _initSize; ++i)
	{
		Connection* p = new Connection();
		p->connect(_ip, _port, _username, _password, _dbname);
		p->refreshAliveTime(); // 刷新一下开始空闲的起始时间
		_connectionQue.push(p);
		_connectionCnt++;
	}

	// 创建生产者线程produce,执行ConnectionPool::produceConnectionTask()函数
	//(注意:linux下,thread => pthread_create)
	thread produce(std::bind(&ConnectionPool::produceConnectionTask, this));
	produce.detach();   // 设置线程为“分离状态”,自行回收线程资源

	// 管理者(定时)线程,扫描超过maxIdleTime时间的空闲连接,并进行连接回收
	thread scanner(std::bind(&ConnectionPool::scannerConnectionTask, this));
	scanner.detach();  // 设置线程为“分离状态”,自行回收线程资源
}

// 生产者线程:等待cv通知,负责当 _connectionQue.empty() && _connectionCnt < _maxSize 时,生产新连接
void ConnectionPool::produceConnectionTask()
{
	for (;;)
	{
		unique_lock<mutex> lock(_queueMutex);
		while (!_connectionQue.empty())
		{
			/* 
				wait()/wait_for():解锁+等待(原子操作)、唤醒和锁定 
				1)解阻塞时,无关乎原因, lock 再次锁定且 wait()/waitfor() 退出。
				2)若此函数通过异常退出,则亦会重获得 lock 。 
			*/
			cv.wait(lock); // 队列不空,此处生产线程进入等待状态
			// 被notify_one()/notify_all()唤醒后,继续while循环
		}

		// 连接数量没有到达上限,继续创建新的连接
		if (_connectionCnt < _maxSize)
		{
			Connection* p = new Connection();
			p->connect(_ip, _port, _username, _password, _dbname);
			p->refreshAliveTime(); // 刷新一下开始空闲的起始时间
			_connectionQue.push(p);
			_connectionCnt++;
		}

		// 通知所有等待的线程
		cv.notify_all();    // 通知消费者线程,可以获取(消费)连接
	}
}

// 从连接池中,获取一个可用的空闲连接
shared_ptr<Connection> ConnectionPool::getConnection()
{
	unique_lock<mutex> lock(_queueMutex);
	// wait_for()==cv_status::no_timeout,则表示被唤醒,需要再次判断_connectionQue连接队列是否为空
	while (_connectionQue.empty())
	{
		/* 
		    wait()/wait_for():解锁+等待(原子操作)、唤醒和锁定
			1)解阻塞时,无关缘由,重获得 lock 并退出 wait()/wait_for()。
			2)若此函数通过异常退出,则亦重获得 lock 。

			在等待的时间_connectionTimeout内,被notify_one()/notify_all()唤醒,则函数立即返回cv_status::no_timeout
			否则,一直等到_connectionTimeout时间到了,才能返回cv_status::timeout
		*/
		if (cv_status::timeout == cv.wait_for(lock, chrono::milliseconds(_connectionTimeout)))
		{
			if (_connectionQue.empty())
			{
				LOG("get connection failure because of the timeout!");
				return nullptr;
			}
		}
	}

	/*
		1、默认情况下,shared_ptr智能指针析构时,会把connection资源直接delete掉,相当于调用connection的析构函数
		2、这里需要自定义shared_ptr的释放资源的方式,即把connection直接归还到queue当中
		   shared_ptr的有参构造函数原型:shared_ptr(_Ux* _Px, _Dx _deleter); 
	*/
	shared_ptr<Connection> sp(_connectionQue.front(), 
		// lambda表达式创建的可调用对象,用作shared_ptr的删除器deleter
		[&](Connection* pcon) {
			// 这里是在服务器应用线程中调用的,所以一定要考虑队列的线程安全操作
			unique_lock<mutex> lock(_queueMutex);
			pcon->refreshAliveTime();         // 刷新一下开始空闲的起始时间
			_connectionQue.push(pcon);
		});

	_connectionQue.pop();

	// 通知所有等待的线程
	cv.notify_all();  // 消费完连接以后,通知生产者线程produceConnectionTask(),继续生产连接,有可能达到最大连接上限maxSize无法再生产
	
	return sp;
}

// 管理者线程,“定时”扫描超过maxIdleTime时间的空闲连接,并进行连接回收
void ConnectionPool::scannerConnectionTask()
{
	for (;;)
	{
		// 通过sleep_for模拟“定时_maxIdleTime”效果
		this_thread::sleep_for(chrono::seconds(_maxIdleTime));

		// 扫描整个队列,_connectionCnt > _initSize && p->getAliveTime() >= (_maxIdleTime * 1000),则释放多余的连接
		unique_lock<mutex> lock(_queueMutex);
		while (_connectionCnt > _initSize)
		{
			Connection* p = _connectionQue.front();
			if (p->getAliveTime() >= (_maxIdleTime * 1000))  // 统一使用毫秒ms为单位
			{
				_connectionQue.pop();
				_connectionCnt--;
				delete p;    // 调用~Connection()释放连接
			}
			else
			{
				break;       // 注意:队头的连接没有超过_maxIdleTime,其它连接肯定没有!!!
			}
		}
	}
}

连接池压力测试:

单线程测试:

未使用线程池:

int main_single_thread_unused()
{ 
	clock_t begin = clock();
	
    // 通过修改循环次数,进行测试
	for (int i = 0; i < 10000; ++i)
	{
		Connection conn;
		char sql[1024] = { 0 };
		sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");
		conn.connect("127.0.0.1", 3306, "root", "211810", "chat");
		conn.update(sql); 
	}

	clock_t end = clock();
	cout << (end - begin) << " ms" << endl;

	return 0;
}

使用线程池:

int main_single_thread_used()
{  
	clock_t begin = clock();
	 
	ConnectionPool* cp = ConnectionPool::initConnectionPool();
	for (int i = 0; i < 10000; ++i)
	{ 
		char sql[1024] = { 0 };
		sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");
			
		// 当结束一个for循环,sp会被释放,即重新添加到_connectionQue连接队列尾部
		shared_ptr<Connection> sp = cp->getConnection();
		sp->update(sql);
	}      

	clock_t end = clock();
	cout << (end - begin) << "ms" << endl;

	return 0;
}

多线程测试:

未使用线程池:

int main_multi_thread_unused()
{
	Connection conn;
	conn.connect("127.0.0.1", 3306, "root", "211810", "chat");

	clock_t begin = clock();

	thread t1([]() {
		for (int i = 0; i < 2000; ++i)
		{ 
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			Connection conn;
			conn.connect("127.0.0.1", 3306, "root", "211810", "chat");
			conn.update(sql);
		}
	});
	thread t2([]() {
		for (int i = 0; i < 2000; ++i)
		{
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			Connection conn;
			conn.connect("127.0.0.1", 3306, "root", "211810", "chat");
			conn.update(sql);
		}
	});
	thread t3([]() {
		for (int i = 0; i < 2000; ++i)
		{ 
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			Connection conn;
			conn.connect("127.0.0.1", 3306, "root", "211810", "chat");
			conn.update(sql);
		}
	});
	thread t4([]() {
		for (int i = 0; i < 2000; ++i)
		{ 
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			Connection conn;
			conn.connect("127.0.0.1", 3306, "root", "211810", "chat");
			conn.update(sql);
		}
	});
	thread t5([]() {
		for (int i = 0; i < 2000; ++i)
		{ 
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			Connection conn;
			conn.connect("127.0.0.1", 3306, "root", "211810", "chat");
			conn.update(sql);
		}
	});

	// 主线程阻塞等待,四个子线程结束
	t1.join(); t2.join(); t3.join(); t4.join(); t5.join();

	clock_t end = clock();
	cout << (end - begin) << " ms" << endl;

	return 0;
}

使用线程池:

int main_multi_thread_used()
{ 
	clock_t begin = clock();

	thread t1([]() {
		ConnectionPool* cp = ConnectionPool::initConnectionPool();
		for (int i = 0; i < 2000; ++i)
		{
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			shared_ptr<Connection> sp = cp->getConnection();
			sp->update(sql);
		}
	});
	thread t2([]() {
		ConnectionPool* cp = ConnectionPool::initConnectionPool();
		for (int i = 0; i < 2000; ++i)
		{
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			shared_ptr<Connection> sp = cp->getConnection();
			sp->update(sql);
		}
	});
	thread t3([]() {
		ConnectionPool* cp = ConnectionPool::initConnectionPool();
		for (int i = 0; i < 2000; ++i)
		{
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			shared_ptr<Connection> sp = cp->getConnection();
			sp->update(sql);
		}
	});
	thread t4([]() {
		ConnectionPool* cp = ConnectionPool::initConnectionPool();
		for (int i = 0; i < 2000; ++i)
		{
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			shared_ptr<Connection> sp = cp->getConnection();
			sp->update(sql);
		}
	});
	thread t5([]() {
		ConnectionPool* cp = ConnectionPool::initConnectionPool();
		for (int i = 0; i < 2000; ++i)
		{
			char sql[1024] = { 0 };
			sprintf(sql, "insert into stuinfo(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male");

			shared_ptr<Connection> sp = cp->getConnection();
			sp->update(sql);
		}
	});

	// 主线程阻塞等待,四个子线程结束
	t1.join(); t2.join(); t3.join(); t4.join(); t5.join();

	clock_t end = clock();
	cout << (end - begin) << " ms" << endl;

	return 0;
}

测试结果:

通过插入操作,衡量使用连接池对性能的影响:

未使用连接池 使用连接池
2500 单线程:2075 ms 五线程:666 ms 单线程:1128 ms 五线程:377 ms
5000 单线程:4003 ms 五线程:1289 ms 单线程:2216 ms 五线程:712 ms
10000 单线程:8110 ms 五线程:2542 ms 单线程:4365 ms 五线程:1389 ms

注意:每次测试完,需要用truncate删除表重来,避免主键自增列过大,导致插入失败。

结论:

观察上述结果知,在单线程/多线程的表现下,使用连接池的对性能的提升几乎差不多。

内存池的实现:

为何需要内存池?

  • 防止小块内存频繁的分配、释放,造成大量的内存碎片的出现;
  • 尤其是当分配大量的小块内存时,内存池对内存的利用率的提升是非常有效的;

分析SGI STL二级空间配置器内存池实现原理:

SGI STL包含了一级空间配置器和二级空间配置器:

  • 一级空间配置器allocator采用mallocfree来管理内存,和C++标准库中提供的allocator是一样的;
  • 二级空间配置器allocator采用了基于freelist自由链表原理的内存池机制实现内存管理,且是线程安全的(空间配置器是供容器使用的,而容器的对象可能在多个线程中使用,故需要保证是线程安全的);

SGI STL内存分配源码
SGI STL的完整源码

空间配置器的相关定义:

这里以SGI STLvector源码为例:

// 容器的默认空间配置器是__STL_DEFAULT_ALLOCATOR( _Tp),是一个宏定义
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc>

// __STL_DEFAULT_ALLOCATOR( _Tp)是一个宏定义,如下:
# ifndef __STL_DEFAULT_ALLOCATOR
# 	ifdef __STL_USE_STD_ALLOCATORS
# 		define __STL_DEFAULT_ALLOCATOR(T) allocator< T >   // 一级空间配置器
# 	else
# 		define __STL_DEFAULT_ALLOCATOR(T) alloc            // 二级空间配置器
# 	endif
# endif

// 一级空间配置器内存管理类 -- 通过malloc和free管理内存
template <int __inst>
class __malloc_alloc_template; 

// 二级空间配置器内存管理类 -- 通过自定义内存池实现内存管理
template <bool threads, int inst>
class __default_alloc_template; 

二级空间配置器源码分析:

重要的类型和变量定义:

// 内存池的粒度信息:自由链表从8字节开始,每次增加一倍,直到128字节
enum {_ALIGN = 8};
enum {_MAX_BYTES = 128};
enum {_NFREELISTS = 16};


// 每一个内存chunk块的头信息
union _Obj {
	union _Obj* _M_free_list_link;
	char _M_client_data[1]; /* The client sees this. */
};
// 组织所有自由链表的数组,数组的每一个元素的类型是_Obj*,全部初始化为0
static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];
 
 
// Chunk allocation state. 记录内存chunk块的分配情况
static char* _S_start_free;
static char* _S_end_free;
static size_t _S_heap_size;

template <bool __threads, int __inst>
char* __default_alloc_template<__threads, __inst>::_S_start_free = 0;
template <bool __threads, int __inst>
char* __default_alloc_template<__threads, __inst>::_S_end_free = 0;
template <bool __threads, int __inst>
size_t __default_alloc_template<__threads, __inst>::_S_heap_size = 0;

重要的辅助接口函数:

/*将 __bytes 上调至最邻近的 8 的倍数*/
static size_t _S_round_up(size_t __bytes)
{ return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }

/*返回 __bytes 大小的chunk块位于 free-list 中的编号*/
static size_t _S_freelist_index(size_t __bytes) 
{ return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1); }

内存池管理函数:

// 分配内存的入口函数
static void* allocate(size_t __n);

/* 极其重要的函数!!! */
// 分配相应内存字节大小的chunk块,并且给_S_start_free、_S_end_free、_S_heap_size这三个成员变量初始化
static char* _S_chunk_alloc(size_t __size, int& __nobjs);


// 返回一个chunk块,并将剩余分配好的chunk块以“单链表”形式连接,并添加到自由链表中
static void* _S_refill(size_t __n);

// 把chunk块归还到内存池
static void deallocate(void* __p, size_t __n);

// 内存池“扩容/瘦身函数”
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::reallocate(void* __p, size_t __old_sz, size_t __new_sz);

SGI STL二级空间配置器内存池的优点:

三大池式组件实现_第3张图片

  1. 对于每一个字节数的chunk块分配,都分为两部分(一部分使用,另一部分备用),备用部分可给当前字节数使用,也可给其他字节数使用
  2. 对于备用内存池划分完chunk块后,如果还有剩余的很小的内存块,再次分配时,会把这些小的内存块再次分配出去,至此备用内存池已使用完;
  3. 指定字节数分配失败后,有一个异常处理过程:对所有的字节的chunk块进行查看,如果哪个字节数有空闲的,则直接借一个出去;
  4. 如果上述过程还是分配失败,则还会调用一个预先设置好的malloc内存分配的回调函数oom_malloc

自实现stl_alloc功能:

项目源码

__malloc_alloc_template.h
#pragma once
#include 

// 封装了malloc和free操作,可设置OOM释放内存的回调函数
template <int __inst>
class __malloc_alloc_template
{ 
private: 
	static void* _S_oom_malloc(size_t);
	static void* _S_oom_realloc(void*, size_t); 
	static void(*__malloc_alloc_oom_handler)();  
public: 
	static void* allocate(size_t __n)
	{
		void* __result = malloc(__n);
		if (0 == __result) 
		{
			__result = _S_oom_malloc(__n);
		}
		return __result;
	}

	static void deallocate(void* __p, size_t /* __n */)
	{
		free(__p);
	}

	static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
	{
		void* __result = realloc(__p, __new_sz);
		if (0 == __result) 
		{
			__result = _S_oom_realloc(__p, __new_sz);
		}
		return __result;
	}

	static void(*__set_malloc_handler(void(*__f)()))()
	{
		void(*__old)() = __malloc_alloc_oom_handler;
		__malloc_alloc_oom_handler = __f;
		return(__old);
	}
};

// malloc_alloc out-of-memory handling 
template <int __inst>
void(*__malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = nullptr;

template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{
	void(*__my_malloc_handler)();
	void* __result;

	for (;;) {
		__my_malloc_handler = __malloc_alloc_oom_handler;

		if (0 == __my_malloc_handler) { throw std::bad_alloc(); }

		(*__my_malloc_handler)();
		__result = malloc(__n);
		if (__result) 
		{
			return(__result);
		}
	}
}

template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
{
	void(*__my_malloc_handler)();
	void* __result;

	for (;;) {
		__my_malloc_handler = __malloc_alloc_oom_handler;
		if (0 == __my_malloc_handler) { throw std::bad_alloc(); }

		(*__my_malloc_handler)();
		__result = realloc(__p, __n);
		if (__result) 
		{
			return(__result);
		}
	}
}
MyAllocator.h
#pragma once
#include 
#include "__malloc_alloc_template.h"

/* 内存池的粒度信息:自由链表从8字节开始,每次增加一倍,直到128字节 */
#define _ALIGN 8 
#define _MAX_BYTES 128
#define _NFREELISTS 16 

typedef __malloc_alloc_template<0> malloc_alloc;

template <typename T>
class MyAllocator
{
public:
	using value_type = T;

	constexpr MyAllocator() noexcept {}
	constexpr MyAllocator(const MyAllocator&) noexcept = default;
	template <typename U>
	constexpr MyAllocator(const MyAllocator<U>&) noexcept {}

	// 分配内存的入口函数:__n为元素类型为T的元素个数(与c++底层容器空间配置器的调用有关)
	T* allocate(size_t __n) {
		__n = __n * sizeof(T);   // 注意!!!
		void* __ret = 0;

		if (__n > (size_t)_MAX_BYTES) {
			__ret = malloc_alloc::allocate(__n);
		} else {
			_Obj* volatile* __my_free_list = _S_free_list + _S_freelist_index(__n);
			
			std::lock_guard<std::mutex> mtx_guard(mtx);

			_Obj* __result = *__my_free_list;
			if (__result == 0) {
				__ret = _S_refill(_S_round_up(__n));
			} else {
				*__my_free_list = __result->_M_free_list_link;
				__ret = __result;
			}
		}

		return (T*)__ret;
	}

	// 把chunk块归还到内存池
	void deallocate(void* __p, size_t __n) {
		if (__n > (size_t)_MAX_BYTES) {
			malloc_alloc::deallocate(__p, __n);
		} else {
			_Obj* volatile*  __my_free_list = _S_free_list + _S_freelist_index(__n);
			_Obj* __q = (_Obj*)__p;

			// acquire lock
			std::lock_guard<std::mutex> mtx_guard(mtx);

			// 头插
			__q->_M_free_list_link = *__my_free_list;
			*__my_free_list = __q;
			// lock is released here
		}
	} 

	// 内存池“扩容/瘦身函数” 
	void* reallocate(void* __p, size_t __old_sz, size_t __new_sz) {
		void* __result;
		size_t __copy_sz;

		if (__old_sz > (size_t)_MAX_BYTES && __new_sz > (size_t)_MAX_BYTES) {
			return (realloc(__p, __new_sz));
		}

		if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) { return(__p); }

		__result = allocate(__new_sz);
		__copy_sz = __new_sz > __old_sz ? __old_sz : __new_sz;
		memcpy(__result, __p, __copy_sz);
		deallocate(__p, __old_sz);
		return(__result);
	}

	// 对象的构造和销毁
	void construct(T* __P, const T& val) {
		/* 
			placement new操作符: 
			void* operator new(std::size_t size, void* ptr);
		*/
		new(__P)T(val);
	}
	// 对象的析构
	void destroy(T* __P) {
		// 手动调用T的析构函数
		__P->~T();
	}
private:
	/*将 __bytes 上调至最邻近的 8 的倍数*/
	static size_t _S_round_up(size_t __bytes);


	/*返回 __bytes 大小的chunk块位于 free-list 中的编号*/
	static size_t _S_freelist_index(size_t __bytes);


	/* 极其重要的函数!!! */
	// 分配相应内存字节大小的chunk块,并且给_S_start_free、_S_end_free、_S_heap_size这三个成员变量初始化
	static char* _S_chunk_alloc(size_t __size, int& __nobjs);


	/* 返回一个chunk块,并将剩余分配好的chunk块以“单链表”形式连接,并添加到自由链表中 */
	static void* _S_refill(size_t __n);


	/* Chunk allocation state. 记录内存chunk块的分配情况 */
	static char* _S_start_free;
	static char* _S_end_free;
	static size_t _S_heap_size; 
	 
	// 每一个内存chunk块的头信息
	union _Obj {
		union _Obj* _M_free_list_link;  // 下一个chunk块的地址
		char _M_client_data[1];   /* The client sees this. */
	};


	/* 内存池基于freelist实现,需要互斥锁,保证线程安全 */
	static std::mutex mtx;

	/* 组织所有自由链表的数组,数组的每一个元素的类型是_Obj*,全部初始化为0 */
	static _Obj* volatile _S_free_list[_NFREELISTS];
	/*
		volatile关键字的作用:
		1、禁止编译器对该变量的缓存或寄存器优化。
		如果变量被声明为volatile,编译器就不能将其缓存到寄存器中,而必须每次都从内存中读取变量的值。

		2、保证变量的访问顺序。
		如果程序中存在多个线程或进程同时访问同一个变量,并且这个变量被声明为volatile,
		那么编译器就会保证每个线程或进程按照指定的顺序访问该变量,避免出现“竞争”条件。
	*/
};
template <typename T>
char* MyAllocator<T>::_S_start_free = nullptr;
template <typename T>
char* MyAllocator<T>::_S_end_free = nullptr;
template <typename T>
size_t MyAllocator<T>::_S_heap_size = 0;

template <typename T>
size_t MyAllocator<T>::_S_freelist_index(size_t __bytes)
{
	return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);
}

template <typename T>
size_t MyAllocator<T>::_S_round_up(size_t __bytes)
{
	return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));
}


template <typename T>
typename MyAllocator<T>::_Obj* volatile MyAllocator<T>::_S_free_list[_NFREELISTS] = {nullptr};


template <typename T>
void* MyAllocator<T>::_S_refill(size_t __n)
{
	int __nobjs = 20;
	char* __chunk = _S_chunk_alloc(__n, __nobjs);
	_Obj* volatile* __my_free_list;
	_Obj* __result;
	_Obj* __current_obj;
	_Obj* __next_obj;
	int __i;

	if (1 == __nobjs) { return(__chunk); }
	__my_free_list = _S_free_list + _S_freelist_index(__n);

	/* Build free list in chunk */
	__result = (_Obj*)__chunk;
	*__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
	for (__i = 1; ; __i++) {
		__current_obj = __next_obj;
		__next_obj = (_Obj*)((char*)__next_obj + __n);
		if (__nobjs - 1 == __i) {
			__current_obj->_M_free_list_link = 0;
			break;
		}
		else {
			__current_obj->_M_free_list_link = __next_obj;
		}
	}
	return(__result);
}


template <typename T>
char* MyAllocator<T>::_S_chunk_alloc(size_t __size, int& __nobjs)
{
	char* __result;
	size_t __total_bytes = __size * __nobjs;
	size_t __bytes_left = _S_end_free - _S_start_free;

	if (__bytes_left >= __total_bytes) {
		__result = _S_start_free;
		_S_start_free += __total_bytes;
		return(__result);
	}
	else if (__bytes_left >= __size) {
		__nobjs = (int)(__bytes_left / __size);
		__total_bytes = __size * __nobjs;
		__result = _S_start_free;
		_S_start_free += __total_bytes;
		return(__result);
	}
	else {
		size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
		// Try to make use of the left-over piece.
		if (__bytes_left > 0) {
			_Obj* volatile* __my_free_list = _S_free_list + _S_freelist_index(__bytes_left);

			((_Obj*)_S_start_free)->_M_free_list_link = *__my_free_list;
			*__my_free_list = (_Obj*)_S_start_free;
		}
		_S_start_free = (char*)malloc(__bytes_to_get);
		if (nullptr == _S_start_free) {
			size_t __i;
			_Obj* volatile* __my_free_list;
			_Obj* __p;
			// Try to make do with what we have.  That can't hurt.  
			// We do not try smaller requests, since that tends to result in disaster on multi-process machines.
			for (__i = __size;
				__i <= (size_t)_MAX_BYTES;
				__i += (size_t)_ALIGN) {
				__my_free_list = _S_free_list + _S_freelist_index(__i);
				__p = *__my_free_list;
				if (0 != __p) {
					*__my_free_list = __p->_M_free_list_link;
					_S_start_free = (char*)__p;
					_S_end_free = _S_start_free + __i;
					return (_S_chunk_alloc(__size, __nobjs));
					// Any leftover piece will eventually make it to the right free list.
				}
			}
			_S_end_free = nullptr;	// In case of exception.
			_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
			// This should either throw an exception or remedy the situation.  
			// Thus we assume it succeeded.
		}
		_S_heap_size += __bytes_to_get;
		_S_end_free = _S_start_free + __bytes_to_get;
		return(_S_chunk_alloc(__size, __nobjs));
	}
}

template <typename T>
std::mutex MyAllocator<T>::mtx;
main.cpp
#include 
#include "MyAllocator.h"
using namespace std;

int main()
{ 
	vector<int, MyAllocator<int>> vctor;

	for (int i = 0; i < 1000; ++i)
	{
		vctor.push_back(rand() % 1000);
	}

	int count = 0;
	for (auto& vctor_ : vctor)
	{ 
		if (count != 0 && count % 10 == 0)
		{
			cout << endl; 
		}
		cout << vctor_ << "\t"; 
		count++;
	}
	cout << endl;

	return 0;
} 

nginx的内存池实现原理:

nginx_1.12.2_palloc源码
nginx_1.12.1完成源码的下载地址

nginx内存池源码分析:

  1. Nginx的内存池模块在./src/core/ngx_palloc.h./src/core/ngx_palloc.c文件中,分别定义了“相关的结构体”和“主要函数实现”。
  2. 注意:nginx内存池,不用考虑线程安全的问题
    为了保证高可靠性和可用,Nginx不是多线程的,而是多进程的,每个进程独立的创建内存池。
  3. 为何Nginx不回收小内存?
  • 在时间和空间上取舍,Nginx觉得小内存本身并不怎么占用空间,且回收还影响速度。
  • 另外,它并不作为主进程的内存池,且子进程给每个连接分配的内存池,在连接断开后,大小内存都会回收,并不会造成内存泄露。

内存池的关系图:

三大池式组件实现_第4张图片

重要类型定义:

typedef struct ngx_pool_s ngx_pool_t;
// nginx内存池的主结构体类型(即首个小块内存的头信息)
struct ngx_pool_s {
	ngx_pool_data_t d; // 内存块的数据头信息
	size_t max;              // 小块内存分配的最大值(不能超过一个页面的大小4k),即ngx_pool_data_t可分配的最大内存值,超过此值则使用ngx_pool_large_t分配内存
	ngx_pool_t *current; // 小块内存池入口指针
	ngx_chain_t *chain; // 挂接一个ngx_chain_t结构
	ngx_pool_large_t *large; // 大块内存分配入口指针
	ngx_pool_cleanup_t *cleanup; // 清理函数handler的入口指针,释放内存池时的callback函数
	ngx_log_t *log;  // 日志相关,暂不作研究
};
// 小块内存数据头信息
typedef struct {
	u_char *last; // 可分配内存开始位置
	u_char *end;  // 可分配内存末尾位置
	ngx_pool_t *next;  // 保存下一个内存池的地址
	ngx_uint_t failed; // 记录当前内存池分配失败的次数
} ngx_pool_data_t;

typedef struct ngx_pool_large_s ngx_pool_large_t;
// 大块内存类型定义
struct ngx_pool_large_s {
	ngx_pool_large_t *next; // 下一个大块内存
	void *alloc; // 记录分配的大块内存的起始地址
};
typedef void (*ngx_pool_cleanup_pt)(void *data); // 清理回调函数的类型定义

typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
// 清理操作的类型定义,包括一个清理回调函数、传给回调函数的数据和下一个清理操作的地址
struct ngx_pool_cleanup_s {
	ngx_pool_cleanup_pt handler; // 回调函数指针
	void *data;                  // 传递给回调函数的指针参数
	ngx_pool_cleanup_t *next;    // 指向下一个回调函数
}; 
// Nginx内存池支持通过自定义的回调函数,对外部资源的清理。
// ngx_pool_cleanup_t是回调函数结构体,它在内存池中以链表形式保存,在内存池进行销毁时,循环调用这些回调函数对数据进行清理。

重要函数接口:

// 创建内存池
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log); 

// 销毁内存池
void ngx_destroy_pool(ngx_pool_t *pool); 

// 重置内存池
void ngx_reset_pool(ngx_pool_t *pool); 

// 内存分配函数,支持内存对齐
void *ngx_palloc(ngx_pool_t *pool, size_t size); 

// 内存分配函数,不支持内存对齐
void *ngx_pnalloc(ngx_pool_t *pool, size_t size); 

// 内存分配函数,支持内存初始化0 
void *ngx_pcalloc(ngx_pool_t *pool, size_t size); 

// 内存释放(大块内存)
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);

// 得到一个ngx_pool_cleanup_t(其设置handler清理函数、data待清理的数据),并头插入pool->cleanup链表中
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);

ngx_create_pool创建内存池:

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log)
{
    // 为当前内存池分配第一块内存
    ngx_pool_t  *p;		
	
    // 调用nginx的字节对齐内存分配函数,为p分配size大小的内存块
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    if (p == NULL) {
        return NULL;
    }
	
    // last跨过内存块中ngx_pool_t结构体,指向紧接着的可分配内存的起始位置
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    // end指向当前size大小内存块的末尾
    p->d.end = (u_char *) p + size;
    p->d.next = NULL;
    p->d.failed = 0;

	// 该语句执行后,size为该内存块可分配的内存大小
    size = size - sizeof(ngx_pool_t);  
    /* 
    	#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
    	即x86中,页大小为4K,内存池最大不超过4095 
    */
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;   

    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}
// `alignment`主要是针对部分`unix`平台需要动态的对齐,大多数情况下编译器和C库都会帮我们处理对齐问题。
#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)

void *ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p = malloc(size);
    if (p == NULL) {
		ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "malloc(%uz) failed", size);
    }
 
	ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
 	return p;
}

ngx_palloc内存分配函数(支持内存对齐):

// 在内存分配时,针对用户所申请的内存的大小,分别“小内存”和“大内存”两种分配策略。
void *ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}
ngx_palloc_small小块内存分配
// 申请小块内存,遍历pool->next链表,判断各内存块是否有满足的可分配空间
// 有则直接分配,否则需要重新申请一个小内存块
static ngx_inline void *ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;

    // 从首个内存块开始寻找
    p = pool->current;

    do {
        m = p->d.last;

        // 对齐处理
        if (align) {
			 /*
			 	// 小块内存分配考虑字节对齐时的单位
			    #define NGX_ALIGNMENT sizeof(unsigned long)   // platform word  
				#define ngx_align_ptr(p, a) (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
			*/
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }
		
        // 当前内存块的剩余内存 >= 所需内存
        if ((size_t) (p->d.end - m) >= size) {
            // 分配内存,并将last指针移向新位置
            p->d.last = m + size; 
            return m;
        }
		
        // 当前内存块的剩余内存 < 所需内存,则去下一个内存块中寻找
        p = p->d.next;

    } while (p);

    // 遍历所有内存块,都没有合适的内存空间,则需分配新的内存块
    return ngx_palloc_block(pool, size);
}
// 向系统申请新的内存块,满足failed机制情况下,将其添加到链表(头结点为首个内存块中的next)“尾部”
static void *ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;
 
    // 计算首个内存块的大小
    psize = (size_t) (pool->d.end - (u_char *) pool);
 
    // 为新的内存块分配psize大小的内存块
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }
 	
    // 将申请的内存块指针转换为ngx_pool_t指针
    new = (ngx_pool_t *) m;
 
    // 初始化新内存块ngx_pool_data_t中,end、next和faile字段
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;
 
    // 指针m移动到可分配内存的开始位置,即需要排除掉ngx_pool_data_t结构体字段占用的内存
    m += sizeof(ngx_pool_data_t);
    // 通过移动m指针,进行对齐处理
    /* 
    	#define NGX_ALIGNMENT   sizeof(unsigned long) 
    	#define ngx_align_ptr(p, a) (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
	*/
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    // 分配size字节后,则新内存块的last指向剩余可用内存空间的首地址
    new->d.last = m + size;
  
  	/* failed的控制机制,保证前面的内存块基本处于満的状态,仅剩余部分小块区域。*/
    // “尾插”:找到pool->next为头结点的链表的最后一个节点,并将new挂在其后
    for (p = pool->current; p->d.next; p = p->d.next) {
    	// 在以pool->next为头结点的链表中,某个内存块的failed次数超过4次,则调整首个内存块为其后的内存块(即“之前的内存块不能再参与分配”)
        if (p->d.failed++ > 4) {  // 各个内存块,一旦遍历到这里就需要对其failed字段加一
            pool->current = p->d.next;
        }
    } 
    p->d.next = new;
 
    return m;
}
ngx_palloc_large大块内存分配:
/* 
	申请大于max的内存块,分配内存后:
	1、放入链表前三个中,有空闲的alloc中
	2、否则,将重新申请一个ngx_pool_large_t后将其放入alloc,并其“头插”到大块内存链表(头结点为pool->large)中
*/
static void *ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;
    // 向系统申请一块size大小的内存空间 
    p = ngx_alloc(size, pool->log);  // ngx_alloc函数内部封装着malloc
    if (p == NULL) {
        return NULL;
    }
 
    n = 0;
    // 在以首个内存块中large为头结点的链表中,ngx_pool_large_t结构体的alloc为空的节点
    // 如果有,则将刚才分配的空间交由它管理  
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }
 		
        // 为提高效率,如果查找3次仍未找到,则直接跳出循环直接创建
        if (n++ > 3) {
            break;
        }
    }
	
	// 此处,本质上会去调用ngx_palloc_small函数
    large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
 
    large->alloc = p;
    // 大块内存的指针”头插“到以pool->large为头节点的链表中
    large->next = pool->large;
    pool->large = large;
 
    return p;
}

结合该大块内存分配函数,可修正nginx内存池的关系图:
三大池式组件实现_第5张图片

ngx_pfree内存释放:

  1. 大块内存的释放:
  • ngx_pfree函数,内部仅仅对指定的大块内存进行释放。
  • 因为大内存块的分配,只检查前3个内存块(考虑效率问题)中alloc是否指向NULL,否则就直接分配内存,所以大内存块的释放必须及时
  1. 小块内存的处理:
  • nginx内存池手动申请的小块内存,在该内存池中并不提供相应小块内存的释放操作,小块内存的分配是通过last指针的偏移完成的
  1. nginx内存池和SGI STL内存池的使用场景分析:
  • nginx主要用于http服务器,即短链接的服务器(客户端发起请求,服务端处理后nginx给客户端发送响应,之后会主动断开tcp连接(http 1.1之后,keep-alive:60s后,期间不再收到客户端请求才能断开tcp连接,否则重置该时间))此时nginx可调用ngx_reset_pool重置内存池,并等待下一次连接…

    弊端:如果是长链接的话,不断地通信会逐渐申请小块内存,但并未真正释放,则最终会将内存耗尽。

  • 如果是tcp长链接,则需要采用SGI STL中的二级空间配置器,每次处理完立即释放连接所占的内存空间。虽然其也可用于短链接,但由于需要释放,故效率低于nginx内存池。

ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;
	// 遍历并将pool->large指向的大块内存链表,全部释放
    for (l = pool->large; l; l = l->next) {
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
            ngx_free(l->alloc);   // 本质上就是free()
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

ngx_reset_pool内存池重置:

// 重置,即将内存池中的大块内存链表、小块内存链表内的可分配内存空间全部释放出来 
void ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;
	
    // 将pool->large指向的大块内存链表,全部释放
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }
	// 重置小块内存:并不调用free将内存交还给系统,只是指针的复位操作
	/* 
	// 源码提供的重置小块内存的方法:没有考虑内存池中除了首个内存块外,其余的内存块的头信息只是ngx_pool_data_t结构体,故会造成一定的空间浪费
    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }*/
	/* 优化后:*/
	// 1、重置内存池中,首个内存块
	p = this->_pool;
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    p->d.failed = 0;
    // 2、重置内存池中,其他的内存块
    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_data_t);
        p->d.failed = 0;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

ngx_destroy_pool销毁内存池:

// 得到一个ngx_pool_cleanup_t(其设置handler清理函数、data待清理的数据),并头插入pool->cleanup链表中
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;

    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        } 
    } else {
        c->data = NULL;
    }

    c->handler = NULL;
    
    // 头插
    c->next = p->cleanup; 
    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}

内存池的销毁操作中,该函数会循环调用ngx_pool_cleanup_s中的handler函数释放资源,并释放大块内存和小块内存链表的内存块。

ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

    // 循环调用handler清理函数
    for (c = pool->cleanup; c; c = c->next) {
        if (c->handler) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "run cleanup: %p", c);
            c->handler(c->data);
        }
    }

    // 释放大块内存
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

    // 释放所有内存池中的小块内存
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}

测试用例:

环境配置:

运行在centos 7上,且需要提前解压tar -zxvf nginx-1.16.0.tar.gz后,./configure,之后make编译nginx源码。

test_ngx_memoryPool.c

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
/*
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, ...)
{

}*/

typedef struct Data stData;
struct Data
{
    char *ptr;
    FILE *pfile;
};

void func1(char *p)
{
    printf("malloc free!");
    free(p);
}
void func2(FILE *pf)
{
    printf("close file!");
    fclose(pf);
}
void main()
{
	// max == min(512 - sizeof(ngx_pool_t), 4095(one page size))
    ngx_pool_t* pool = ngx_create_pool(512, NULL);
    if(pool == NULL)
    {
        printf("ngx_create_pool fail...");
        return;
    }

    void* p1 = ngx_palloc(pool, 128);   // 从小块内存池分配的
    if(p1 == NULL)
    {
        printf("ngx_palloc 128 bytes fail...");
        return;
    }

    stData* p2 = ngx_palloc(pool, 512); // 从大块内存池分配的
    if(p2 == NULL)
    {
        printf("ngx_palloc 512 bytes fail...");
        return;
    }
    p2->ptr = malloc(12);
    strcpy(p2->ptr, "hello world");
    p2->pfile = fopen("data.txt", "w");
    
    // 添加两个ngx_pool_cleanup_t,其中指定了handler清理回调函数和待删除数据data
    ngx_pool_cleanup_t* c1 = ngx_pool_cleanup_add(pool, sizeof(char*));
    c1->handler = func1;
    c1->data = p2->ptr;

    ngx_pool_cleanup_t* c2 = ngx_pool_cleanup_add(pool, sizeof(FILE*));
    c2->handler = func2;
    c2->data = p2->pfile;

    ngx_destroy_pool(pool); // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存

    return;
}

编译连接命令:

// compile: where is include files 
gcc -c ngx_testpool.c -o ngx_testpool.o -g -I /opt/nginx-1.16.0/src/core -I /opt/nginx-1.16.0/src/event -I /opt/nginx-1.16.0/src/event/modules -I /opt/nginx-1.16.0/src/os/unix -I /opt/nginx-1.16.0/objs -I /opt/nginx-1.16.0/src/http -I src/http/modules

// dynamic link:
gcc -o ngx_testpool ngx_testpool.o /opt/nginx-1.16.0/objs/src/core/ngx_palloc.o /opt/nginx-1.16.0/objs/src/os/unix/ngx_alloc.o

自实现nginx_palloc的功能:

项目源码
完全按照nginx源码的内存分配策略实现。

ngx_memoryPool.h

#pragma once

// 类型重定义:
using u_char = unsigned char;
using ngx_uint_t = unsigned int;

// 一个页的大小4K
# define ngx_pagesize 4096   
// 小块内存能分配的最大的大小
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)  
// 默认nginx内存池开辟的大小为16K
#define NGX_DEFAULT_POOL_SIZE (16 * 1024)   

// 内存池大小按照16bytes进行内存对齐
#define NGX_POOL_ALIGNMENT 16
// 将小块内存最小的size,调整为NGX_POOL_ALIGNMENT临近的倍数
#define NGX_MIN_POOL_SIZE ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), NGX_POOL_ALIGNMENT)

// 小块内存分配考虑字节对齐时的单位
#define NGX_ALIGNMENT   sizeof(unsigned long)    /* platform word */ 
// 将数值x调整到数值y的临近的倍数
#define ngx_align(x, y) (((x) + (y - 1)) & ~(y - 1))
// 将指针p内存对齐到a的临近的倍数
#define ngx_align_ptr(p, a) (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

// 将buf缓冲区置为0/c
#define ngx_memzero(buf, n)       (void) memset(buf, 0, n)
#define ngx_memset(buf, c, n)     (void) memset(buf, c, n)

struct ngx_pool_t;

/* 小块内存数据头信息 */
struct ngx_pool_data_t  {
	u_char* last; // 可分配内存开始位置
	u_char* end;  // 可分配内存末尾位置
	ngx_pool_t* next;  // 保存下一个内存池的地址
	size_t failed; // 记录当前内存池分配失败的次数
};

/* 大块内存数据头信息 */
struct ngx_pool_large_t {
	ngx_pool_large_t *next; // 下一个大块内存
	void *alloc; // 记录分配的大块内存的起始地址
};

typedef void(*ngx_pool_cleanup_pt)(void*data); // 清理回调函数的类型定义
 /*
	清理操作的类型定义:
	1、Nginx内存池支持通过自定义的回调函数,对外部资源的清理。
	2、ngx_pool_cleanup_t是回调函数结构体,它在内存池中以链表形式保存,在内存池进行销毁时,循环调用这些回调函数对数据进行清理。
*/
struct ngx_pool_cleanup_t {
	ngx_pool_cleanup_pt handler; // 清理回调函数指针
	void *data;                  // 传递给回调函数的参数
	ngx_pool_cleanup_t *next;    // 指向下一个清理回调函数
};

/* nginx内存池的主结构体类型(即首个小块内存的头信息)*/
struct ngx_pool_t {
	ngx_pool_data_t d;          // 内存块的数据头信息
	size_t max;                  // 小块内存分配的最大值(不能超过一个页面的大小4K),即ngx_pool_data_t可分配的最大内存值,超过此值则使用ngx_pool_large_t分配内存
	ngx_pool_t* current;         // 小块内存池入口指针 
	ngx_pool_large_t* large;      // 大块内存分配入口指针
	ngx_pool_cleanup_t* cleanup;   // 清理函数handler的入口指针,释放内存池时的callback函数 
};


class ngx_memoryPool
{
public:
	// 创建size大小的内存池
	bool ngx_create_pool(size_t size);

	// 从内存池中分配size大小的内存,支持内存对齐
	void* ngx_palloc(size_t size);

	// 从内存池中分配size大小的内存,不支持内存对齐
	void* ngx_pnalloc(size_t size);

	// 从内存池中分配size大小的内存,支持内存初始化0 
	void* ngx_pcalloc(size_t size);

	// 大块内存释放、小块内存只做指针移动调整
	void ngx_pfree(void* p);
	 
	// 得到一个ngx_pool_cleanup_t,将其设置handler清理函数、data待清理的数据
	ngx_pool_cleanup_t *ngx_pool_cleanup_add(size_t size);

	// 销毁内存池
	void ngx_destroy_pool();

	// 重置内存池,即将内存池中的大块内存链表、小块内存链表内的可分配内存空间全部释放出来 
	void ngx_reset_pool();
private:
	ngx_pool_t* _pool;
	 

	/* 内存分配的机制:小块、大块内存的分配 */
	/*
		申请小块内存,遍历pool->next链表,判断各内存块是否有满足的可分配空间:
		有则直接分配,否则需要重新申请一个小内存块
	*/
	void* ngx_palloc_small(size_t size, ngx_uint_t align);
	// 向系统申请新的内存块,满足failed机制情况下,将其添加到链表(头结点为首个内存块中的next)“尾部”
	void* ngx_palloc_block(size_t size);

	/*
		申请大于max的大块内存:
		1、遍历大块内存链表(头结点为pool->large),判断是否存在节点的alloc==NULL的情况,有则将申请的内存块存放在该节点
		2、否则,加入大块内存链表(头结点为pool->large)尾部
	*/
	void* ngx_palloc_large(size_t size);
};

ngx_memoryPool.cpp

#include 
#include "ngx_memoryPool.h"

// 创建size大小的内存池
bool ngx_memoryPool::ngx_create_pool(size_t size)
{
	// 为当前内存池分配第一块内存
	ngx_pool_t* p;

	// 调用nginx的字节对齐内存分配函数,为p分配size大小的内存块
	p = (ngx_pool_t*)malloc(size);
	if (p == nullptr) 
	{
		return false;
	}

	// last跨过内存块中ngx_pool_t结构体,指向紧接着的可分配内存的起始位置
	p->d.last = (u_char*)p + sizeof(ngx_pool_t);
	// end指向当前size大小内存块的末尾
	p->d.end = (u_char*)p + size;
	p->d.next = nullptr;
	p->d.failed = 0;

	// 该语句执行后,size为该内存块可分配的内存大小
	size = size - sizeof(ngx_pool_t);
	/*
		#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
		即x86中,页大小为4K,内存池最大不超过4095
	*/
	p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

	p->current = p; 
	p->large = nullptr;
	p->cleanup = nullptr;

	this->_pool = p;
	return true;
} 


/* 从内存池中分配size大小的内存,支持内存对齐 */
void* ngx_memoryPool::ngx_palloc(size_t size)
{
	// 在内存分配时,针对用户所申请的内存的大小,分别“小内存”和“大内存”两种分配策略
	if (size <= this->_pool->max)
	{
		return this->ngx_palloc_small(size, (ngx_uint_t)1);
	}
	return this->ngx_palloc_large(size);
}

// 从内存池中分配size大小的内存,不支持内存对齐
void* ngx_memoryPool::ngx_pnalloc(size_t size)
{
	// 在内存分配时,针对用户所申请的内存的大小,分别“小内存”和“大内存”两种分配策略
	if (size <= this->_pool->max)
	{
		return this->ngx_palloc_small(size, (ngx_uint_t)1);
	}
	return this->ngx_palloc_large(size);
}

// 从内存池中分配size大小的内存,支持内存初始化0 
void* ngx_memoryPool::ngx_pcalloc(size_t size)
{
	void* p = ngx_palloc(size);
	if (p != nullptr)
	{
		ngx_memzero(p, size);
	}
	return p;
}


/* 内存分配的机制:小块、大块内存的分配 */
// 申请小块内存 
void* ngx_memoryPool::ngx_palloc_small(size_t size, ngx_uint_t align)
{
	u_char      *m;
	ngx_pool_t  *p;

	// 从首个内存块开始寻找
	p = this->_pool->current;

	do {
		m = p->d.last;

		// 对齐处理
		if (align) {
			// NGX_ALIGNMENT ==> 小块内存分配考虑字节对齐时的单位
			// ngx_align_ptr将指针m进行内存对齐到NGX_ALIGNMENT的倍数
			m = ngx_align_ptr(m, NGX_ALIGNMENT);
		}

		// 当前内存块的剩余内存 >= 所需内存
		if ((size_t)(p->d.end - m) >= size) {
			// 分配内存,并将last指针移向新位置
			p->d.last = m + size;
			return m;
		}

		// 当前内存块的剩余内存 < 所需内存,则去下一个内存块中寻找
		p = p->d.next;

	} while (p);

	// 遍历所有内存块,都没有合适的内存空间,则需分配新的内存块
	return ngx_palloc_block(size);
}
// 向系统申请新的内存块(用来分配小块内存),满足failed机制情况下,将其添加到链表(头结点为首个内存块中的next)“尾部”
void* ngx_memoryPool::ngx_palloc_block(size_t size)
{
	// 向系统申请新的内存块,满足failed机制情况下,将其添加到链表(头结点为首个内存块中的next)“尾部” 
	u_char      *m;
	size_t       psize;
	ngx_pool_t  *p, *new_memBlk;

	// 计算首个内存块的大小
	psize = (size_t)(this->_pool->d.end - (u_char *)this->_pool);

	// 为新的内存块分配psize大小的内存块
	m = (u_char*)malloc(psize);
	if (m == nullptr) {
		return nullptr;
	}

	// 将申请的内存块指针转换为ngx_pool_t指针
	new_memBlk = (ngx_pool_t *)m;

	// 初始化新内存块ngx_pool_data_t中,end、next和faile字段
	new_memBlk->d.end = m + psize;
	new_memBlk->d.next = NULL;
	new_memBlk->d.failed = 0;

	// 指针m移动到可分配内存的开始位置,即需要排除掉ngx_pool_data_t结构体字段占用的内存
	m += sizeof(ngx_pool_data_t);
	// 通过移动m指针,进行对齐处理
	/*
		#define NGX_ALIGNMENT   sizeof(unsigned long)
		#define ngx_align_ptr(p, a) (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
	*/
	m = ngx_align_ptr(m, NGX_ALIGNMENT);
	// 分配size字节后,则新内存块的last指向剩余可用内存空间的首地址
	new_memBlk->d.last = m + size;

	/* failed的控制机制,保证前面的内存块基本处于満的状态,仅剩余部分小块区域。*/
	// “尾插”:找到pool->next为头结点的链表的最后一个节点,并将new挂在其后
	for (p = this->_pool->current; p->d.next; p = p->d.next) {
		// 在以pool->next为头结点的链表中,某个内存块的failed次数超过4次,则调整首个内存块为其后的内存块(即“之前的内存块不能再参与分配”)
		if (p->d.failed++ > 4) {  // 各个内存块,一旦遍历到这里就需要对其failed字段加一
			this->_pool->current = p->d.next;
		}
	}
	p->d.next = new_memBlk;

	return m;
}

// 申请大于max的大块内存
void* ngx_memoryPool::ngx_palloc_large(size_t size)
{
	/*
		申请大于max的内存块,分配内存后:
		1、放入链表前三个(考虑了效率问题)中,有空闲的alloc中
		2、否则,将重新申请一个ngx_pool_large_t后将其放入alloc,并其“头插”到大块内存链表(头结点为pool->large)中
	*/
	void              *p;
	ngx_uint_t         n;
	ngx_pool_large_t  *large;
	// 向系统申请一块size大小的内存空间 
	p = malloc(size);
	if (p == NULL) {
		return NULL;
	}

	n = 0;
	// 在以首个内存块中large为头结点的链表中,ngx_pool_large_t结构体的alloc为空的节点
	// 如果有,则将刚才分配的空间交由它管理  
	for (large = this->_pool->large; large; large = large->next) {
		if (large->alloc == NULL) {
			large->alloc = p;
			return p;
		}

		// 为提高效率,如果查找3次仍未找到,则直接跳出循环直接创建
		if (n++ > 3) {
			break;
		}
	}

	// 此处,本质上会去调用ngx_palloc_small函数
	large = (ngx_pool_large_t*)ngx_palloc(sizeof(ngx_pool_large_t));
	if (large == nullptr) {
		free(p);
		return nullptr;
	}

	large->alloc = p;
	// 大块内存的指针”头插“到以pool->large为头节点的链表中
	large->next = this->_pool->large;
	this->_pool->large = large;

	return p;
}


// 重置内存池,即将内存池中的大块内存链表、小块内存链表内的可分配内存空间全部释放出来 
void ngx_memoryPool::ngx_reset_pool()
{
	ngx_pool_t        *p;
	ngx_pool_large_t  *l;

	// 将pool->large指向的大块内存链表,全部释放
	for (l = this->_pool->large; l; l = l->next)
	{
		if (l->alloc)
		{
			free(l->alloc);
		}
	}
	// 重置小块内存:并不调用free将内存交还给系统,只是指针的复位操作
	/*
	// 源码提供的重置小块内存的方法:没有考虑内存池中除了首个内存块外,其余的内存块的头信息只是ngx_pool_data_t结构体,故会造成一定的空间浪费
	for (p = this->_pool; p; p = p->d.next) {
		p->d.last = (u_char*) p + sizeof(ngx_pool_t);
		p->d.failed = 0;
	}*/
	/* 优化后:*/
	// 1、重置内存池中,首个内存块
	p = this->_pool;
	p->d.last = (u_char*)p + sizeof(ngx_pool_t);
	p->d.failed = 0;
	// 2、重置内存池中,其他的内存块
	for (p = this->_pool; p; p = p->d.next) 
	{
		p->d.last = (u_char *)p + sizeof(ngx_pool_data_t);
		p->d.failed = 0;
	}

	this->_pool->current = this->_pool;
	this->_pool->large = NULL;
}

// 大块内存释放、小块内存只做指针移动调整
void ngx_memoryPool::ngx_pfree(void* p)
{
	ngx_pool_large_t * l;
	// 遍历并将pool->large指向的大块内存链表,全部释放
	for (l = this->_pool->large; l; l = l->next) {
		if (p == l->alloc) {
			free(l->alloc); l->alloc = nullptr;
			return;
		}
	}
}

// 得到一个ngx_pool_cleanup_t(其设置handler清理函数、data待清理的数据),并头插入pool->cleanup链表中
ngx_pool_cleanup_t* ngx_memoryPool::ngx_pool_cleanup_add(size_t size)
{
	ngx_pool_cleanup_t* c;

	c = (ngx_pool_cleanup_t*)ngx_palloc(sizeof(ngx_pool_cleanup_t));
	if (c == nullptr) 
	{
		return nullptr;
	}

	if (size) {
		c->data = ngx_palloc(size);
		if (c->data == nullptr)
		{
			return nullptr;
		} 
	} else {
		c->data = nullptr;
	}

	c->handler = nullptr;

	// 头插
	c->next = this->_pool->cleanup; 
	this->_pool->cleanup = c;

	return c;
}

// 销毁内存池
void ngx_memoryPool::ngx_destroy_pool() 
{ 
	ngx_pool_t          *p, *n;
	ngx_pool_large_t    *l;
	ngx_pool_cleanup_t  *c;

	// 循环调用handler清理函数
	for (c = this->_pool->cleanup; c; c = c->next) 
	{
		if (c->handler) 
		{ 
			c->handler(c->data);
		}
	}

	// 释放大块内存
	for (l = this->_pool->large; l; l = l->next) 
	{
		if (l->alloc) 
		{
			free(l->alloc);
		}
	}

	// 释放所有内存池中的小块内存
	for (p = this->_pool, n = this->_pool->d.next; /* void */; p = n, n = n->d.next)
	{
		free(p);

		if (n == NULL) 
		{
			break;
		}
	}
} 

main.cpp

#include 
#include 
#include "ngx_memoryPool.h"
using namespace std;
 
typedef struct Data stData;
struct Data
{
	char *ptr;
	FILE *pfile;
};

void func1(void* _p)
{
	char* p = (char*)_p;
	cout << "malloc free!" << endl;
	free(p);
}
void func2(void* _pf)
{
	FILE* pf = (FILE*)_pf;
	cout << "close file!" << endl;
	fclose(pf);
}

int main(void)
{
	ngx_memoryPool ngx_mem_pool;

	// max == min(512 - sizeof(ngx_pool_t), 4095(one page size))
	bool ret = ngx_mem_pool.ngx_create_pool(512);
	if (!ret)
	{
		printf("ngx_create_pool fail...");
		return -1;
	}

	void* p1 = ngx_mem_pool.ngx_palloc(128);   // 从小块内存池分配的
	if (p1 == NULL)
	{
		printf("ngx_palloc 128 bytes fail...");
		return -1;
	}

	stData* p2 = (stData*)ngx_mem_pool.ngx_palloc(512); // 从大块内存池分配的
	if (p2 == NULL)
	{
		printf("ngx_palloc 512 bytes fail...");
		return -1;
	}
	p2->ptr = (char*)malloc(12);
	strcpy(p2->ptr, "hello world");
	p2->pfile = fopen("data.txt", "w");

	// 添加两个ngx_pool_cleanup_t,其中指定了handler清理回调函数和待删除数据data
	ngx_pool_cleanup_t* c1 = ngx_mem_pool.ngx_pool_cleanup_add(sizeof(char*));
	c1->handler = func1;
	c1->data = p2->ptr;

	ngx_pool_cleanup_t* c2 = ngx_mem_pool.ngx_pool_cleanup_add(sizeof(FILE*));
	c2->handler = func2;
	c2->data = p2->pfile;

	ngx_mem_pool.ngx_destroy_pool(); // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存

	return 0;
} 

你可能感兴趣的:(c++,中间件,nginx)