list.h

在libhv库中,有一个双向链表的实现,刚看的时候感觉有点疑惑,因为这个链表没有数据,只有前指针和后指针,跟过去学的不太一样,于是仔细分析了一下。

首先是结构体:

struct list_head {
	struct list_head *next, *prev;
};
#define list_node   list_head

该结构体不包含数据信息。

下面的初始化

static inline void list_init(struct list_head *list)
{
	list->next = list;
	list->prev = list;
}

初始化就是将prev和next都指向自己,假如我要初始化list_head类型的p1,list_init(&p1),初始化后是这样的:

哈哈,随便画了画,凑合着看吧,这里假设p1的指针是100,那么prev==next==100,初始化后p1就成了链表的head。

添加成员

static inline void __list_add(struct list_head *n,
			      struct list_head *prev,
			      struct list_head *next)
{
	next->prev = n;
	n->next = next;
	n->prev = prev;
	prev->next = n;
}

static inline void list_add(struct list_head *n, struct list_head *head)
{
	__list_add(n, head, head->next);
}

static inline void list_add_tail(struct list_head *n, struct list_head *head)
{
	__list_add(n, head->prev, head);
}

其中__list_add是内部接口,list_add是前插入,list_add_tail是尾插入。先说list_add,假设我要在上面的p1的基础上添加一个p2,调用list_add(&p2, &p1),会间接调用__list_add,这里head和head->next都是指向的p1。在经过__list_add的4步指针操作后,就成了这个样子:

注意这里的p1是链首,p2才是真正意义上的第一个节点,所以这里p2在p1的后面与前插入并不矛盾。如果再插入一个p3,就能看出来了。再添加一个p3,这时候调用__list_add的第二个参数指向p1,第三个参数指向p2,经过4步指针操作,得到的结果为:

next方向, 表示为p1->p3->p2->p1

prev方向,表示为p1->p2->p3->p1,

p3添加到了p1和p2的中间,也就是说,如果以p1为首地址,那么以后再添加都是添加到p1的后面,当然也可以添加到其他位置,只要改一改首地址就可以了,毕竟是双向的,无所谓头,无所谓尾。就像上面这个图一样,p1、p2和p3都可以作为首地址。

如果知道了上面的添加操作,理解尾添加就很简单了,以上面的p1、p2和p3为例,如果p1是首元素,那么在p2的后面添加一个元素不就相当于在p2和p1之间添加一个元素,跟以p2为首地址,执行前插入一样。

删除操作

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	prev->next = next;
}

static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
}

还是以p1、p2和p3的图为例,如果要删除p3,不过就是将p2的前指针改为指向p1,p1的后指针改为指向p2,这样p1和p2就连接起来了,就又退回到了p1和p2的那张图,删除操作非常简单。

替换操作

static inline void list_replace(struct list_head *old,
				struct list_head *n)
{
	n->next = old->next;
	n->next->prev = n;
	n->prev = old->prev;
	n->prev->next = n;
}

使用n替换old,替换操作也很简答, 假设我要用p4替换p3,那么要让p4的next指向p2,p2的prev指向p4,p4的prev指向p1,修改p1的next指向p4,就把p3剔除了。

判断某个元素是不是最后一个:

static inline int list_is_last(const struct list_head *list,
				const struct list_head *head)
{
	return list->next == head;
}

根据上面的p1、p2、p3那张图,p2是最后一个,p2的next指向的就是p1,实际上也可以判断p1的prev是不是p2来判断,一样的。

判断链表为空:

static inline int list_empty(const struct list_head *head)
{
	return head->next == head;
}

根据p1那个图,自己指向自己,就是空,这里的空表示只有首元素。

但是下面有个函数挺奇怪的,

static inline int list_empty_careful(const struct list_head *head)
{
	struct list_head *next = head->next;
	return (next == head) && (next == head->prev);
}

根据名称,应该是更小心的判断链表是否为空,方法是比前面的判断多判断了 next==prev,看注释是说这种情况是为了应付另一个cpu也在处理同一个链表的情况,而且只有其他cpu只使用list_del_init()函数才能保证不需要同步机制就能达到安全,注释如下

using list_empty_careful() without synchronization
 * can only be safe if the only activity that can happen
 * to the list entry is list_del_init(). Eg. it cannot be used
 * if another CPU could re-list_add() it.

而list_del_init是这样的:

#define INIT_LIST_HEAD  list_init

static inline void list_del_init(struct list_head *entry)
{
	__list_del_entry(entry);
	INIT_LIST_HEAD(entry);
}

感觉这个所谓的安全指针对是被删除的那个元素,因为list_del_init最后会对该元素初始化,所以如果调用list_del,删除的元素的prev和next是不确定的,所以无法通过prev==next判断是否为空。而之所以说不能使用list_add,可以看上面的p1、p2的那张图,p1的next和prev都指向p2的,所以next==prev仍然是成立的,虽然不是空。暂时先这样理解了,对不对其实我也不确定。。。。

将首元素移动到末尾

static inline void __list_del_entry(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
}

static inline void list_move_tail(struct list_head *list,
				  struct list_head *head)
{
	__list_del_entry(list);
	list_add_tail(list, head);
}

static inline void list_rotate_left(struct list_head *head)
{
	struct list_head *first;

	if (!list_empty(head)) {
		first = head->next;
		list_move_tail(first, head);
	}
}

上面的list_rotate_left函数是将第一个节点移动到末尾。以上面p1、p3、p2的图举例,就是将第一个元素p3移动到p2的后面,不要忘记p1是head,并不是第一个节点,p3才是第一个节点。方法就是先将第一个节点p3从链表中删除,然后再将该节点尾插入末尾。

判断是否只有一个元素

static inline int list_is_singular(const struct list_head *head)
{
	return !list_empty(head) && (head->next == head->prev);
}

list_is_singular判断是否链表只有一个节点,很简单,前面的p1、p2的那张图,除了作为head的p1,只有一个节点p2,这时候p1的prev和next都是指向的p2,所以不为空,并且prev==next就表示只有一个节点。

 

基本的功能差不多就这些,其他的功能感觉不怎么用的到,先不看了。看一些在libhv中怎么使用的吧,还记得最开始说的吗,这个链表没有数据,所以在使用的时候,跟平时在书上讲的那种有数据的链表是不一样的,在nlog.c文件中, 有一个相关用法。结构体定义如下:


typedef struct network_logger_s {
    hloop_t*            loop;
    hio_t*              listenio;
    struct list_head    clients;
} network_logger_t;

typedef struct nlog_client {
    hio_t*              io;
    struct list_node    node;
} nlog_client;

static network_logger_t s_logger = {0};

network_logger_t就是链表的头,nlog_client就是链表的节点。也就是说,如果想使用链表,只要将自己需要的数据和链表定义成一个结构体就可以了。

初始化很简单:

list_init(&s_logger.clients);

添加链表元素:

    nlog_client* client;
    HV_ALLOC_SIZEOF(client);
    client->io = io;

    hmutex_lock(&s_mutex);
    list_add(&client->node, &s_logger.clients);

就是要注意不管是初始化还是添加数据,都是使用结构体中的链表成员,不要直接使用自己定义的结构体。那么有个问题,就是链表中记录的是结构体中链表成员的位置,如何获取除了链表之外,自己定义的数据呢?

void network_logger(int loglevel, const char* buf, int len) {
    struct list_node* node;
    nlog_client* client;

    hmutex_lock(&s_mutex);
    list_for_each (node, &s_logger.clients) {
        client = list_entry(node, nlog_client, node);
        hio_write(client->io, buf, len);
    }
    hmutex_unlock(&s_mutex);
}

#define list_for_each(pos, head) \
	for (pos = (head)->next; prefetch(pos->next), pos != (head); \
        	pos = pos->next)

#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

#define container_of(ptr, type, member) \
((type*)((char*)(ptr) - offsetof(type, member)))

最上面是nlog.c中执行的遍历,下面是涉及相关宏定义。可以忽略list_for_each中的prefetch,list_for_each就是简单的链表遍历,重点是list_entry,该宏就是通过链表获取刚才定义的结构体。将宏定义展开就是:((type*)((char*)(node) - offsetof(nlog_client, node))),注意list_entry(node, nlog_client, node)前后两个node表示不同的意思,第一个node表示list_node类型的指针,第二个node表示nlog_client结构体中定义的node成员。offsetof表示获取node成员在结构体nlog_client中的偏移量。也就是说我想从链表中获取自己定义的nlog_client,方法就是使用链表节点的指针,减去该节点所在结构体的偏移量,就获得了该结构体的首地址。

最后是删除操作,比较简单,就是别忘了释放内存。

    nlog_client* client = (nlog_client*)hevent_userdata(io);
    if (client) {
        hevent_set_userdata(io, NULL);

        hmutex_lock(&s_mutex);
        list_del(&client->node);
        hmutex_unlock(&s_mutex);

        HV_FREE(client);
    }

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值