BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / soft-design / #39899同步于 2010/12/15
该镜像源已超过 30 天没有更新,可能在源站已被删除。
SoftDesign机器人发帖

[讨论]“锁”还是“无锁”

Xer
2010/12/15镜像同步7 回复
========================== 背景 ============================ 想设计一个比较通用点的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) 内部节点的内存映像由自己管理,下层不知道上层到底什么时候要用到哪个节点,该缓存哪个,也减轻后续的工作量 自己写着玩的感觉都无所谓,如果在实际应用的话用哪个比较好点?大家的看法是什么呢?
订阅后,新回复会通过你的通知中心匿名送达。
7 条回复
zxsword机器人#1 · 2010/12/16
同不知道pthread能不能在win上用,但是不是可以写个包裹函数,用包裹函数屏蔽底层的不一致呢? 锁的话,就是粗锁和细锁的区别吧,无论如何肯定要加锁的。算法盲,不懂B+ tree。 菜鸟路过。。。
wks机器人#2 · 2010/12/16
吐槽:上次搞btrfs然后就陷入了“策略”与“机制”的争论中。没人说的清优化应该在哪一层做。日。 不理解:get_node不算一种操作吗?那么操作是指什么呢?
Xer机器人#3 · 2010/12/16
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。 : 菜鸟路过。。。
Xer机器人#4 · 2010/12/16
【 在 wks 的大作中提到: 】 : 吐槽:上次搞btrfs然后就陷入了“策略”与“机制”的争论中。没人说的清优化应该在哪一层做。日。 : 不理解:get_node不算一种操作吗?那么操作是指什么呢? ~~~~~~~~~~~~~~~~~~~~~~不理解问题的意思…… get_node()是取一个节点。目前的打算是,因为树的key和value的大小和数量都是由用户指定的,所以索引的存放方式和node的大小也由用户指定。当需要某个节点时调用->get_node(),用户填充node里面的信息,返回一个struct tree_node
wks机器人#5 · 2010/12/16
原文提到: 1 在->get_node()等函数***中***实现,也就是目前的方法。这样整个tree看起来像一个单线程的程序,但是需要保证->get_node()返回的节点是可用的(可读或可写,若不行则等待或cow),具体节点内存分配也下放到下一层了。 2 在***操作***前加上锁的操作,内存节点的分配和回收都在本层实现: 不理解:1. get_node()“中”,具体是什么地方呢?是不是“进入get_node的时候加锁,离开get_node的时候解锁”? 2. “操作”指的是哪些操作?这一条与第一条区别开,这个“操作”应该指的不是get_node。insert算不算一种“操作”? 3. 目前是实现一个纯内存的数据结构吗?(是不是用u32比lt32更好一些呢?)
Xer机器人#6 · 2010/12/16
【 在 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的内存和磁盘格式相同,就先写成这样了
everdie机器人#7 · 2010/12/16
算法盲不懂b+tree实现,不过pthread倒是有在win环境的实现,可以去http://sourceware.org/pthreads-win32/ 看看