nginx学习七 高级数据结构之动态数组ngx_array_t

本文详细介绍了nginx内部使用的ngx_array_t数组结构,包括其定义、成员变量含义及操作函数ngx_array_create、ngx_array_destroy、ngx_array_push等的实现原理与使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 ngx_array_t结构

ngx_array_t是nginx内部使用的数组结构。nginx的数组结构在存储上与大家认知的C语言内置的数组有相似性,比如实际上存储数据的区域也是一大块连续的内存。但是数组除了存储数据的内存以外还包含一些元信息来描述相关的一些信息,并且可以动态增长。下面

我们从数组的定义上来详细的了解一下。ngx_array_t的定义位于src/core/ngx_array.c|h里面。

struct ngx_array_s {
    void        *elts;//数组的首地址
    ngx_uint_t   nelts;//数组中已经使用的元素个数
    size_t       size; //每个元素占用的内存大小
    ngx_uint_t   nalloc;//当前数组中能够容纳元素的个数
    ngx_pool_t  *pool;  //内存池对象
};
elts指向存储数据内存的起始地址。

nelts是数组中实际已经存储的元素个数

size是每个数组元素占用内存的大小,比如int占用4个字节的大小,size=4。

nalloc是数组空间大小。

pool成员函数负责管理数组使用的内存。

2函数操作

nginx为数组提供了五个操作函数如下:

//创建一个动态数组,数组的大小为n,每个元素的大小为size
ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);

//销毁已分配的动态数组元素空间和动态数组对象
void ngx_array_destroy(ngx_array_t *a);

//向数组中添加一个元素,返回这个新元素的地址,如果数组空间已经用完,数组会自动扩充空间
void *ngx_array_push(ngx_array_t *a);

//向数组中添加n个元素,返回这n个元素中第一个元素的地址
void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);

//和create函数的功能差不多,只不过这个array不能为空,返回值为是否初始化成功
static ngx_inline ngx_int_t
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)

2.1ngx_array_create


ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
创建一个数组,p是内存池对象, n为数组存储元素的个数, size为每个元素占用的空间大小。

来看看源代码:

ngx_array_t *
ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size)
{
    ngx_array_t *a;

	//1:创建ngx_array_t指针,这个array的内存也是在p上申请的
    a = ngx_palloc(p, sizeof(ngx_array_t));
    if (a == NULL) {
        return NULL;
    }

	//2:申请数组存储元素的内存
    a->elts = ngx_palloc(p, n * size);
    if (a->elts == NULL) {
        return NULL;
    }

    //初始化成员
    a->nelts = 0;
    a->size = size;
    a->nalloc = n;
    a->pool = p;

    return a;//返回数组指针
}

2.2 ngx_array_destroy

void ngx_array_destroy(ngx_array_t *a);

回收已分配给数组的内存,包括数组本身。看源代码:

void
ngx_array_destroy(ngx_array_t *a)
{
    ngx_pool_t  *p;

    p = a->pool;

   //1:销毁数组存储元素的内存,即数据区的内存
    if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
        p->d.last -= a->size * a->nalloc;
    }

	//2:销毁数组本身的内存,即结构体array本身的内存
	//a = ngx_palloc(p, sizeof(ngx_array_t));这句代码申请的内存
    if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {
        p->d.last = (u_char *) a;
    }
}
2.3 ngx_array_push

void *ngx_array_push(ngx_array_t *a);

向数组中添加元素,并且返回新增加元素的地址。看源码:

void *
ngx_array_push(ngx_array_t *a)
{
    void        *elt, *new;
    size_t       size;
    ngx_pool_t  *p;

    if (a->nelts == a->nalloc) {//数组已满
        size = a->size * a->nalloc;
        p = a->pool;

        if ((u_char *) a->elts + size == p->d.last
            && p->d.last + a->size <= p->d.end)//如果p的剩余空间>=一个数组元素的空间,就分配一个空间给数组
        {
            p->d.last += a->size;//调整pool的last,即修改下一次可分配空间的其实地址
            a->nalloc++;

        } else {
            new = ngx_palloc(p, 2 * size);//申请新的空间,大小是原来的2倍,假如pool的内存不足够分配一个新的数组元素
            if (new == NULL) {
                return NULL;
            }

            ngx_memcpy(new, a->elts, size);//把原有的元素拷贝到新分配的内存区
            a->elts = new;//修改数组数据区的地址,使其指向新分配的内存区
            a->nalloc *= 2;//修改数组可容纳的元素个数,是原来容纳元素的2倍
        }
    }

    elt = (u_char *) a->elts + a->size * a->nelts;//新增加元素的地址
    a->nelts++;//数组中元素的个数加1

    return elt;//返回新增加元素的地址
}

调用这个函数并没用真的添加进元素,它只是返回新加元素将要被放入数组的地址,我们必须按如下操作才能真正的添加如元素:

//添加一个int元素
ngx_int_t* elem;
elem = ngx_array_push(array);//得到新增元素地址
*elem = 10;

2.4ngx_array_push_n

void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);

向数组中添加n个元素,返回这n个元素中第一个元素的地址。


void *
ngx_array_push_n(ngx_array_t *a, ngx_uint_t n)
{
    void        *elt, *new;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *p;

    size = n * a->size;

    if (a->nelts + n > a->nalloc) {//数组已满

        p = a->pool;

        if ((u_char *) a->elts + a->size * a->nalloc == p->d.last
            && p->d.last + size <= p->d.end)//如果pool剩余的内存能够容纳这n个元素,就不用重新分配内存
        {
            p->d.last += size;//修改last使其指向可分配内存的起始地址
            a->nalloc += n;//数组容纳元素个数+n

        } else {//如果pool剩余的内存不能够容纳这n个元素,就重新分配内存

            nalloc = 2 * ((n >= a->nalloc) ? n : a->nalloc);//申请2倍的内存

            new = ngx_palloc(p, nalloc * a->size);
            if (new == NULL) {
                return NULL;
            }

            ngx_memcpy(new, a->elts, a->nelts * a->size);//把原有的元素拷贝到新申请的内存中
            a->elts = new;//修改数组元素区的地址
            a->nalloc = nalloc;//修改数组能够容纳的元素个数
        }
    }

    elt = (u_char *) a->elts + a->size * a->nelts;//新增元素的首地址
    a->nelts += n;//已存储元素个数+n

    return elt;
}


2.5 ngx_array_init

ngx_int_t ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size);

和create函数的功能差不多,只不过这个array不能为空,返回值为是否初始化成功

static ngx_inline ngx_int_t
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    /*
     * set "array->nelts" before "array->elts", otherwise MSVC thinks
     * that "array->nelts" may be used without having been initialized
     */
    //初始化array,array不能为空
    array->nelts = 0;
    array->size = size;
    array->nalloc = n;
    array->pool = pool;

    array->elts = ngx_palloc(pool, n * size);//申请内存空间
    if (array->elts == NULL) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

3 ngx_array_t的一个使用

void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);

/*
author: smtl
date: 2014-09-28
*/

#include <stdio.h>
#include <stdlib.h>
#include <ngx_core.h>
#include <ngx_config.h>
#include <ngx_conf_file.h>
#include <nginx.h>
#include <ngx_string.h>
#include <ngx_palloc.h>
#include <ngx_array.h>

////////////////////////////////////////////////////////////////////////////////////
//不加下面这两个定义编译会出错
volatile ngx_cycle_t  *ngx_cycle;

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

int main()//too many migical number
{
	ngx_pool_t* pool = ngx_create_pool(2048, NULL);
	if (pool == NULL)
	{
        printf("create pool failed\n");
        exit(1);
	}

	ngx_array_t* array = ngx_array_create(pool, 20, sizeof(ngx_int_t));
	if (array == NULL)
	{
        printf("array alloc failed\n");
        exit(1);
	}

    ngx_int_t i;
    ngx_int_t* elem;
    for (i=0; i<20; ++i)
    {
        elem = ngx_array_push(array);//添加元素
        *elem = i;
    }

    elem = (int*)array->elts;
    for (i=0; i<20; ++i)
    {
        printf("array[%d] = %d \n", i, elem[i]);
    }

    printf("添加10个元素:\n");
    ngx_int_t n = 10;
    elem = ngx_array_push_n(array, n);
    for (i=0; i<n; ++i)
    {
        elem[i] = 20 + i;
    }

    elem = (int*)array->elts;
    for (i=20; i<20+n; ++i)
    {
        printf("array[%d] = %d \n", i, elem[i]);
    }

    ngx_array_destroy(array);

    printf("ngx_array_int:\n");

    array = ngx_pcalloc(pool, sizeof(ngx_array_t));
    //注意这个函数和ngx_array_create的区别,array不能为空
    ngx_array_init(array, pool, 20, sizeof(ngx_int_t));

    for (i=0; i<20; ++i)
    {
        elem = ngx_array_push(array);//添加元素
        *elem = rand()%1000;
    }

    elem = array->elts;
    for (i=0; i<20; ++i)
    {
        printf("array[%d] = %d \n", i, elem[i]);
    }

    ngx_destroy_pool(pool);

    return 0;
}


http://blog.youkuaiyun.com/xiaoliangsky/article/details/39647771

<think>嗯,用户现在问的是Nginx高级数据结构,特别是ngx_queue_t的用途。之前用户已经询问过关于request_pool_size销毁的原因,可能现在想深入了解Nginx内部的实现机制。我需要先回忆一下ngx_queue_t的相关知识。 首先,ngx_queue_t是Nginx中一个双向链表的实现。用户可能想知道为什么Nginx要自己实现这样的数据结构,而不是使用C语言标准库里的结构。这可能涉及到性能优化、内存管理或者跨平台兼容性的问题。 接着,用户可能对具体的使用场景感兴趣。比如在HTTP模块中,Nginx如何处理请求,哪些部分会用到这种链表结构。需要举一些例子,比如维护定时事件、管理HTTP请求等,说明ngx_queue_t的实际应用。 另外,用户可能想了解这种数据结构的特点,比如轻量级、不侵入数据结构的实现方式。需要解释侵入式和非侵入式链表的区别,以及为什么Nginx选择这种方式。可能还要提到内存管理的优势,比如内存池的配合使用,减少内存碎片。 还要考虑用户可能的疑问,比如与其他数据结构如红黑树的区别。这时候需要指出链表和树的不同应用场景,比如链表适合频繁插入删除,而红黑树适合快速查找。说明Nginx如何根据不同的需求选择不同的数据结构。 另外,用户可能对如何在自己的模块中使用ngx_queue_t感兴趣,可能需要简单的代码示例,但用户问题中没有明确要求,所以可能不需要详细代码,但可以简要说明其使用方式,比如初始化、插入节点等步骤。 最后,总结ngx_queue_t的优势,比如高效、灵活、节省内存,以及为什么在Nginx中这样的设计是合理的,适合高并发场景的需求。同时确保回答结构清晰,符合之前用户要求的层次分明的格式,比如分点说明用途、设计特点、优势比较等。</think>Nginx 内部设计了许多高效的自定义数据结构以适配高并发场景,`ngx_queue_t` 是其中一种双向链表的实现,其核心价值在于**轻量级、无侵入、内存友好**的设计。以下是其关键用途与设计逻辑: --- ### 一、`ngx_queue_t` 的核心用途 1. **轻量级链表操作** - 用于需要频繁插入、删除但无需复杂查找的场景(如定时器事件管理、HTTP 请求阶段处理器链)。 - 示例: - 维护超时事件(`ngx_event_timer_rbtree` 结合红黑树和队列管理)。 - HTTP 请求处理阶段(`phase_engine` 中按顺序调用各阶段处理函数)。 2. **无侵入式设计** - 链表节点(`ngx_queue_t`)**不嵌入业务数据结构**,而是通过指针关联,避免强制耦合。 例如: ```c typedef struct { ngx_queue_t queue; // 链表节点 void *data; // 实际业务数据指针 } ngx_custom_node_t; ``` - 优势:同一业务结构可同时加入多个链表,灵活性高。 3. **内存池友好** - 节点内存通常从 Nginx 的 `ngx_pool_t` 内存池分配,生命周期与请求或连接绑定,避免内存泄漏。 --- ### 二、`ngx_queue_t` 的设计特点 1. **极简结构体** 仅包含前驱(`prev`)和后继(`next`)指针,无额外开销: ```c typedef struct ngx_queue_s ngx_queue_t; struct ngx_queue_s { ngx_queue_t *prev; ngx_queue_t *next; }; ``` 2. **高效操作宏** 提供宏直接操作链表,避免函数调用开销(如 `ngx_queue_insert_head`、`ngx_queue_remove`)。 3. **与红黑树互补** - **`ngx_queue_t`**:适合顺序遍历或频繁增删。 - **`ngx_rbtree_t`**:适合快速查找(如定时器、共享内存键值存储)。 - 两者常结合使用(例如定时器用红黑树管理超时时间,用队列维护活跃事件)。 --- ### 三、对比传统链表的优势 | 特性 | `ngx_queue_t` | 传统链表(如 Linux 内核 `list_head`) | |---------------------|--------------------------------|--------------------------------------| | **内存占用** | 更小(仅 2 指针) | 类似 | | **数据耦合** | 无侵入式 | 通常需嵌入结构体 | | **多链表支持** | 通过指针关联,灵活 | 需预先定义嵌入节点 | | **内存管理** | 深度整合 Nginx 内存池 | 依赖外部分配机制 | --- ### 四、典型应用场景 1. **HTTP 请求处理链** 各处理阶段(如 `NGX_HTTP_SERVER_REWRITE_PHASE`)通过链表串联处理函数。 2. **事件定时器** 超时事件按时间排序存入红黑树,活跃事件通过队列管理。 3. **模块配置合并** 多个模块的配置指令通过链表合并(如 `listen` 指令的 `ngx_listening_t` 列表)。 --- ### 五、使用示例(伪代码) ```c // 定义业务数据结构 typedef struct { int id; ngx_queue_t link; // 链表节点 } ngx_custom_data_t; // 初始化链表 ngx_queue_t head; ngx_queue_init(&head); // 插入节点 ngx_custom_data_t *node = ngx_palloc(pool, sizeof(ngx_custom_data_t)); node->id = 100; ngx_queue_insert_tail(&head, &node->link); // 遍历链表 ngx_queue_t *q; for (q = ngx_queue_head(&head); q != ngx_queue_sentinel(&head); q = ngx_queue_next(q)) { ngx_custom_data_t *data = ngx_queue_data(q, ngx_custom_data_t, link); // 处理 data->id } ``` --- ### 总结 `ngx_queue_t` 是 Nginx 为高并发场景量身定制的双向链表,其**无侵入、低开销、内存池集成**的特性,使其在事件管理、请求处理等核心流程中成为高效的基础设施。与红黑树等结构配合,共同支撑 Nginx 的高性能架构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值