我使用 C 语言和 Linux 作为平台。我想在多个进程中共享多个结构,这些结构具有链接列表的头部(这些列表也应该共享)和彼此的指针。此数据所需的内存可高达 1Mb。因为我不能在共享内存中使用指针,因为它们对不同的进程无效。
有两种选择:1)要么使用偏移值而不是指针。2) 否则,使用不同的共享内存并使用共享内存 ID(由 shmget 返回)而不是指针。
由于要共享的内存大小很大,哪个选项更好?你能建议另一种选择吗?
谢谢。
我使用 C 语言和 Linux 作为平台。我想在多个进程中共享多个结构,这些结构具有链接列表的头部(这些列表也应该共享)和彼此的指针。此数据所需的内存可高达 1Mb。因为我不能在共享内存中使用指针,因为它们对不同的进程无效。
有两种选择:1)要么使用偏移值而不是指针。2) 否则,使用不同的共享内存并使用共享内存 ID(由 shmget 返回)而不是指针。
由于要共享的内存大小很大,哪个选项更好?你能建议另一种选择吗?
谢谢。
使用偏移值。
代替指针,使用size_t
从共享内存区域开始的偏移量(以字符为单位)。您需要在访问或操作这些列表的任何地方执行此操作。
编辑添加:
以这种方式使用偏移量可以在大多数架构上编译为非常高效的代码,并且您可以使用__sync..()
内置函数以原子方式访问和修改它们。请记住对所有访问使用内置函数,包括读取:否则该值可能在非原子读取期间被原子修改(反之亦然),从而导致数据损坏。
如果您知道共享内存的大小永远不会超过 4GB,那么您可以uint32_t
改为使用偏移类型,在 64 位架构上为每个“指针”节省四个字节。如果将所有目标对齐到 32 位边界,则可以将其翻两番至 8GB。
使用的一个非常好的副作用uint32_t
是,您可以在所有 64 位和一些 32 位架构上原子地操作指针对(两个连续的偏移量)。假设您还将共享内存中的所有内容也对齐到 32 位边界,并使用每个 32 位单元的偏移量,您可以使用原子方式获取/设置指针对
static inline void get_pair(void *const base, const uint32_t offset, uint32_t *const pair)
{
uint64_t *const ptr = (uint64_t *)(offset + (uint32_t *)base);
uint64_t val;
val = __sync_or_and_fetch(ptr, (uint64_t)0);
memcpy(pair, &val, sizeof val);
}
static inline void switch_pair(void *const base, const uint32_t offset, const uint32_t *const new, uint32_t *const old)
{
uint64_t *const ptr = (uint64_t *)(offset + (uint32_t *)base);
uint64_t oldval, newval;
memcpy(newval, &new, sizeof newval);
do {
/* Note: this access does not need to be atomic, */
memcpy(oldval, ptr, sizeof oldval);
/* because the next one verifies it. */
} while (!__sync_bool_compare_and_swap(ptr, oldval, newval));
if (old)
memcpy(old, &oldval, sizeof oldval);
}
__sync...()
内置程序至少在 GCC 和 Intel CC 中工作。新的 C11 标准也采用了 C++11 风格__atomic..()
的内置函数,但在当前编译器中实现这些特性需要一些时间。
如果您编写库代码,或者您希望维护几年的代码,它可能会节省您查找两种内置类型的时间,并为您自己(或任何将要维护它的人)添加注释。内置插件之间的转换),以描述如果它们已经可用,您将使用哪个原子内置插件。
最后,请记住,像这样使用共享内存意味着您必须小心谨慎,就像您有多个线程同时访问内存一样。原子操作会有所帮助,如果您可以原子地操作指针对,您可以对列表进行一些非常聪明的技巧,但您仍然需要非常敏锐地了解极端情况和可能的竞争条件。