返回信息流========================== 背景 ============================
想设计一个比较通用点的b+ tree,提供内部节点索引的操作(插入,删除,查找等),需要自己实现具体数据的插入和删除的操作:
[quote]
/* b+ tree node structure
*
* key value
* | |
* v v
* +--------+------+------+-----+------+------+------+-----+------+----------+
* | header | key1 | key2 | ... | keyn | val1 | val2 | ... | valn | val(n+1) |
* +--------+------+------+-----+------+------+------+-----+------+----------+
*
* */
struct tree_node {
le16 nr_key, height; /* header */
le32 *key, *value; /* 数量和大小可以指定 */
};
struct tree_root {
int key_size, value_size;
struct tree_operations *ops;
};
struct tree_operations {
/* ----- index operations ----- */
struct tree_node* (*get_node)(struct tree_root *root, struct tree_node *parent,
le32 idx, void *context);
void (*put_node)(struct tree_root *root, struct tree_node *node, le32 idx,
void *context);
/* ----- data operations ----- */
int (*insert)(le32 *idx, const void *info, int flag);
};
[/quote]
==================================== 想法 ================================
很明显,->get_node(),->insert()都需要锁。问题来了,目前锁的操作有两种想法:
1 在->get_node()等函数中实现,也就是目前的方法。这样整个tree看起来像一个单线程的程序,但是需要保证->get_node()返回的节点是可用的(可读或可写,若不行则等待或cow),具体节点内存分配也下放到下一层了。
[quote]
int tree_insert(struct tree_root *root, le32 key, const void *info)
{
......
/* lock here */
leaf_node = root->ops->get_node(root, parent, parent->value[child_pos],
context);
root->ops->insert(&leaf_node->value[pos], info, INSERT_NEW);
root->ops->put_node(root, leaf_node, parent->value[child_pos], context);
......
}
[/quote]
2 在操作前加上锁的操作,内存节点的分配和回收都在本层实现:
[quote]
int tree_insert(struct tree_root *root, le32 key, const void *info)
{
......
/* lock here */
leaf_node = tree_get_mem_node(parent, node_pos);
root->ops->get_node(root, parent, parent->value[child_pos], leaf_node, context);
root->ops->insert(&leaf_node->value[pos], info, INSERT_NEW);
tree_put_mem_node(leaf_node);
......
}
[/quote]
=========================== 疑问 =============================
方法一(比较灵活):
(1) 不确定性(读写互斥还是copy on write?)
(2) 平台相关(不知道pthread能不能在win上用)
方法二:
(1) 目前大部分程序都要求并发访问,在程序中内置锁能减轻后续开发的工作,方便应用
(2) 内部节点的内存映像由自己管理,下层不知道上层到底什么时候要用到哪个节点,该缓存哪个,也减轻后续的工作量
自己写着玩的感觉都无所谓,如果在实际应用的话用哪个比较好点?大家的看法是什么呢?
这是一条镜像帖。来源:北邮人论坛 / soft-design / #39899同步于 2010/12/15
该镜像源已超过 30 天没有更新,可能在源站已被删除。
SoftDesign机器人发帖
[讨论]“锁”还是“无锁”
Xer
2010/12/15镜像同步7 回复
订阅后,新回复会通过你的通知中心匿名送达。
7 条回复
同不知道pthread能不能在win上用,但是不是可以写个包裹函数,用包裹函数屏蔽底层的不一致呢?
锁的话,就是粗锁和细锁的区别吧,无论如何肯定要加锁的。算法盲,不懂B+ tree。
菜鸟路过。。。
吐槽:上次搞btrfs然后就陷入了“策略”与“机制”的争论中。没人说的清优化应该在哪一层做。日。
不理解:get_node不算一种操作吗?那么操作是指什么呢?
wrapper是个好方法
目前打算只对整个node加锁,不打算细化到某个key的程度,但不排除将来有这样的需求。现在纠结的问题是这锁该是由下层实现呢(在->insert()中实现,树中不显式调用lock()之类的函数),还是由树本身实现(在树中使用tree_lock_node(),或者提供一个->lock_node()的接口)。
如果以后需要细化到某个key的lock,那可能接口就变成这样(参数越来越多,自己都晕了):
[quote]
struct tree_node* (*get_node)(struct tree_root *root, struct tree_node *parent,
le32 idx, int lock_type, void *context);
[/quote]
不知道有没有好的方法?
【 在 zxsword 的大作中提到: 】
: 同不知道pthread能不能在win上用,但是不是可以写个包裹函数,用包裹函数屏蔽底层的不一致呢?
: 锁的话,就是粗锁和细锁的区别吧,无论如何肯定要加锁的。算法盲,不懂B+ tree。
: 菜鸟路过。。。
【 在 wks 的大作中提到: 】
: 吐槽:上次搞btrfs然后就陷入了“策略”与“机制”的争论中。没人说的清优化应该在哪一层做。日。
: 不理解:get_node不算一种操作吗?那么操作是指什么呢?
~~~~~~~~~~~~~~~~~~~~~~不理解问题的意思……
get_node()是取一个节点。目前的打算是,因为树的key和value的大小和数量都是由用户指定的,所以索引的存放方式和node的大小也由用户指定。当需要某个节点时调用->get_node(),用户填充node里面的信息,返回一个struct tree_node
原文提到:
1 在->get_node()等函数***中***实现,也就是目前的方法。这样整个tree看起来像一个单线程的程序,但是需要保证->get_node()返回的节点是可用的(可读或可写,若不行则等待或cow),具体节点内存分配也下放到下一层了。
2 在***操作***前加上锁的操作,内存节点的分配和回收都在本层实现:
不理解:1. get_node()“中”,具体是什么地方呢?是不是“进入get_node的时候加锁,离开get_node的时候解锁”?
2. “操作”指的是哪些操作?这一条与第一条区别开,这个“操作”应该指的不是get_node。insert算不算一种“操作”?
3. 目前是实现一个纯内存的数据结构吗?(是不是用u32比lt32更好一些呢?)
【 在 wks 的大作中提到: 】
: 原文提到:
: 1 在->get_node()等函数***中***实现,也就是目前的方法。这样整个tree看起来像一个单线程的程序,但是需要保证->get_node()返回的节点是可用的(可读或可写,若不行则等待或cow),具体节点内存分配也下放到下一层了。
: 2 在***操作***前加上锁的操作,内存节点的分配和回收都在本层实现:
: 不理解:1. get_node()“中”,具体是什么地方呢?是不是“进入get_node的时候加锁,离开get_node的时候解锁”?
->get_node()是指获取树节点的函数,而->insert()函数则是插入用户自定义数据的操作。如下图,->get_node()是获取tree.idx里的节点,而->insert()则是插入words.idx里的操作。
[quote]
+-----------------------------+
| user-defined methods |
v |
(user data) => (hash key, data) |
+-~-~-~-~-~-~-~-~-~-~-~-~--~-~-~-~-~/~-~-~-~-~-~-~-~-~-~-~-~-~-~-~~-~-~-+ |
| tree.idx: / | |
| / | |
| +-----+-----------+----------+-------------+-----+ | |
| | ... | child (i) | hash (i) | child (i+1) | ... | | |
| +-----+-----------+----------+-------------+-----+ | |
| / \ | |
| +-----+--------+------+-----+ +-----+--------+------+-----+ | |
| | ... | offset | hash | ... | -> | ... | offset | hash | ... | -> ... | |
| +-----+--------+------+-----+ +-----+--------+------+-----+ | |
| / | |
+-~-~-~-~-~-~-~-~-~-~-~-~--~-~-~-~-~-~-~-~-~/~-~-~-~-~-~-~-~-~-~-~~-~-~-+ |
/ |
+--------+-----+----------+------+---------+-----+ |
words.idx: | header | ... | word_len | word | doc_idx | ... | |
+--------+-----+----------+------+---------+-----+ |
/ <-------+
[/quote]
->get_node()是加锁,另有一个->put_node()是解锁。或者这样说,->get_node()返回一个可用的节点,而->put_node()则告诉下层节点已经用完了。一个可能的实现:
[quote]
static struct bpt_node* tree_idx_get_node(struct bptree_root *root,
struct bpt_node *parent, le32 idx, void *context)
{
struct bpt_node *node;
node = mem_alloc(sizeof(struct bpt_node));
read(fd_tree_idx, node, sizeof(struct bpt_node));
/* lock */
return node;
}
static void tree_idx_put_node(struct bptree_root *root, struct bpt_node *node,
le32 idx, void *context)
{
/* unlock */
mem_free(node, sizeof(struct bpt_node));
}
[/quote]
例如一个tree_lookup()函数:
[quote]
int bptree_lookup(struct bptree_root *root, le32 key, const void *second_key,
void *buf, int len)
{
int pos, ret, flag = 1;
struct bpt_node *n;
struct bpt_path *path = NULL, *p;
n = root->bops->get_root(root, context);
if (n == NULL)
return -1;
if (n->height > 0) {
flag = 0;
do {
p = mem_alloc(sizeof(struct bpt_path));
p->node = n;
p->next = path;
path = p;
if (lookup_key(n, key, &pos) < 0 || pos == n->nr_key)
path->pos = pos;
else
path->pos = pos + 1;
n = root->bops->get_node(root, n, n->idx[path->pos], context);
} while (n->height > 0);
}
ret = -1;
if (lookup_key(n, key, &pos) == 0)
ret = root->bops->lookup(n->idx[pos], second_key, buf, len);
if (flag == 0) {
root->bops->put_node(root, n, path->node->idx[path->pos], context);
while (path->next != NULL) {
p = path->next;
root->bops->put_node(root, path->node, p->node->idx[p->pos], context);
mem_free(path, sizeof(struct bpt_path));
path = p;
}
n = path->node;
mem_free(path, sizeof(struct bpt_path));
}
root->bops->put_root(n, context);
return ret;
}
[/quote]
: 2. “操作”指的是哪些操作?这一条与第一条区别开,这个“操作”应该指的不是get_node。insert算不算一种“操作”?
都算操作,只是->get_node()是对tree index(图中的tree.idx)的操作,而->insert()是对user-defined data(图中的words.idx)的操作,见主贴tree_operations的注释
: 3. 目前是实现一个纯内存的数据结构吗?(是不是用u32比lt32更好一些呢?)
不算是纯内存数据结构,tree index也保存在磁盘上,只是tree node的存储方法也需要用户定义。目前的实现tree_node的内存和磁盘格式相同,就先写成这样了
算法盲不懂b+tree实现,不过pthread倒是有在win环境的实现,可以去http://sourceware.org/pthreads-win32/ 看看