可以将boost的进程间段管理器分配器自己与其他进程共享吗?

时间:2017-07-08 05:40:53

标签: c++ boost shared-memory interprocess

我正在使用boost :: interprocess创建一个共享的进程间映射。 为此,我从地图所在的共享内存段的segment_manager创建一个分配器。

map的元素值类型是basic_string,它本身模板化使用从同一段管理器创建的char分配器。 在一个过程中,我创建了地图,在另一个过程中,我使用该过程中的map迭代器搜索项目,在某些情况下,我使用迭代器调用map :: erase。

这会导致访问冲突异常,我知道我正在使用有效的迭代器进行调用。 访问冲突发生在对basic_string的析构函数的调用中,该析构函数是迭代器指向的“对”的“第二个”。 当我在插入后立即在写入过程中使用迭代器执行相同的擦除操作时,没有访问冲突。

看起来好像读取过程正在尝试使用元素的分配器释放元素的内存,该分配器是在写入过程中创建的,而分配器仅在创建它的过程中有效。

这是否意味着分配器本身无法共享?

我原本期望分配器可以在两个进程中使用,因为它的状态应该只包含在两个进程中都有效的相对指针。 如果没有,我如何共享在进程之间使用共享内存(堆)分配的元素? 在将它们传递给basic_string元素之前,我是否应该在编写过程中以特殊方式创建这些分配器,以允许我在另一个进程的anerase操作中使用它们?

还有什么可能导致访问冲突?

2 个答案:

答案 0 :(得分:1)

分配器很好(魔法在offset_ptr,并且它跨过程边界是透明的。)

如果“客户端”破坏了字符串,那么除了阅读之外你还在做其他事情。最有可能的是,您收到了一份副本,例如:

auto by_copy = smap.find(key)->second; // makes a copy

尝试,例如做

auto const& by_ref = smap.find(key)->second; // doesn't copy

或者,您可能正在执行smap[key],如果密钥不在那里,它会自动分配。这可能会导致过时的竞争条件(在进程之间共享数据就像在线程之间共享数据一样:您需要正确的同步)。

最后,你没有提及/关于密钥,但如果它也是一个字符串,那么只需按键查找就很容易从共享内存中分配(而且它是一个临时的,它会破坏)。比赛状况再次出现。另请参阅want to efficiently overcome mismatch between key types in a map in Boost.Interprocess shared memory

演示

在没有合适的SSCCEMCVE的情况下,让我向你扔一个。你可能会发现你做的事情有所不同。

#include <iostream>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/containers/map.hpp>

namespace bip = boost::interprocess;

namespace shared {
    namespace bc  = boost::container;

    using Segment = bip::managed_shared_memory;
    using Manager = Segment::segment_manager;
    template <typename T>
    using Alloc   = bc::scoped_allocator_adaptor<bip::allocator<T, Manager> >;

    using String  = bip::basic_string<char, std::char_traits<char>, Alloc<char> >;
    template <typename K, typename V, typename Cmp = std::less<K> >
        using Map = bip::map<K, V, Cmp, Alloc<std::pair<K const, V> > >;
};

int main() {
    using namespace shared;

    Segment smt(bip::open_or_create, "de06c60a-0b80-4b20-a805-b3f405f35427", 20ul<<20); // 20 mb
    auto& mat = *smt.find_or_construct<Map<String, String> >("dict")(smt.get_segment_manager());

    if (mat.empty()) {
        mat.emplace("1", "one");
        mat.emplace("2", "two");
        mat.emplace("3", "three");
    } else {
        // shared string factory
        auto ss = [&](auto... stuff) { return String(stuff..., smt.get_segment_manager()); };

        auto  copy = mat.at(ss("3")); // constructs and destructs temp String("3"); constructs copy
        auto& ref  = mat.at(ss("2")); // constructs and destructs temp String("2"); no copy
        std::cout << "copy: " << copy << "\n";
        std::cout << "ref: "  << ref  << "\n";

        // iterate with no shared temps or copies:
        for (auto& p : mat)
            std::cout << "entry '" << p.first << "' -> '" << p.second << "'\n";
    } // destructs copy
}

在Coliru上也一样,但内存映射文件(因为那里不允许共享内存):

<强> Live On Coliru

using Segment = bip::managed_mapped_file;

首先打印,后续运行:

copy: three
ref: two
entry '1' -> 'one'
entry '2' -> 'two'
entry '3' -> 'three'

答案 1 :(得分:0)

是的,boost提供的用于共享内存的分配器可以被其他进程使用。 我将首先介绍它的缺点,然后再解释原始问题的原因。

简而言之 - 我使用的是使用char,char_traits和共享内存分配器实例化的basic_string。我应该使用boosts boost :: interprocess :: basic_string。

这里有一些细节。

在我的代码中,我使用boost的进程间映射创建共享映射。该地图中的元素是字符串。 我正确地使用了boost段管理器来为map创建一个分配器,我使用相同的段管理器为放置在该映射中的字符串创建一个char分配器。

但是,我在地图中创建和存储的字符串是模板类型std :: basic_string。我正确地使用分配器类型提供此模板,并且我正确地将allocator实例提供给其构造函数中的字符串。

此外,存储在映射中的字符串可以由打开共享内存的另一个处理器读取。但是,当读取过程尝试擦除条目时,删除字符串会导致访问冲突。

用boost :: interprocess :: basic_string替换basic_string后,这个问题就消失了。

如果我知道boost在进程间命名空间中有自己的basic_string,我肯定会使用它;我已经知道我需要使用boost的地图而不是标准的地图。我只是不知道有一个interprocess :: basic_string

作为旁注,我仍然有兴趣知道为什么std容器显然不适合与共享内存的分配器一起使用,即使它们确实提供了允许实现者指定应该使用哪个分配器的模板参数