【问题标题】:Share std::map between processes with mmap()使用 mmap() 在进程之间共享 std::map
【发布时间】:2016-08-26 16:48:16
【问题描述】:

我正在尝试共享std::map<std::string, std::chrono::system_clock::time_point> map:每个字符串都是一个标识站点的主机名,time_point 是进程最后一次访问该站点的时间。

我正在尝试使用 mmap,但每个进程仍然可以看到自己的地图副本。

这是我的代码(我去掉了所有与我的问题无关的方法和变量):

#include <sys/mman.h>
#include <unistd.h>

#include <iostream>
#include <map>
#include <string>
#include <chrono>

typedef std::map<std::string, std::chrono::system_clock::time_point> mymap;
typedef mymap::iterator iter;
typedef mymap* mapPointer;

class MmapManager {
    private:
        MmapManager() {
            frequency = (mapPointer) mmap(NULL, sizeof(frequency), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
            if (frequency == MAP_FAILED) {
                std::cout << "mapping failed" << std::endl;
            }
        };

        ~MmapManager() {
            std::cout << "~MmapManager()" << std::endl;
        }

    public:
        // my class was designed with the singleton pattern
        static MmapManager& getInstance() {
            static MmapManager instance;
            return instance;
        }

    private:
        // pointer to my map
        mapPointer frequency;

    public:
        // check if the process already visited site "host"
        bool isHostAlreadyVisited(std::string host) {
            return frequency->find(host) != frequency->end();
        }
        // add new visited site and time of the visit
        void addHost(std::string host) {
            (*frequency)[host] = std::chrono::system_clock::now();
            std::cout << "PROC " << getpid() << " added " << host << std::endl;
        }
        // get time of the visit for site "host"
        std::chrono::system_clock::time_point getElement(std::string host) {
            return (*frequency)[host];
        }
        // print the map
        void showMap(void) {
            std::cout << "PROC " << getpid() << " prints map keys" << std::endl;
            for (auto it = frequency->begin(); it != frequency->end(); ++it) {
                std::cout << it->first << std::endl;
            }
        }
};

int main(void) {

    // simulate the processes
    for (int i=0; i<5; i++) {
        // child process
        if (fork() == 0) {
            // if child never visited this site...
            if (! MmapManager::getInstance().isHostAlreadyVisited("www.google.com")) {
                std::cout << "PID " << getpid() << " www.google.com is new" << std::endl;
                // ...add it to the map
                MmapManager::getInstance().addHost("www.google.com");
            }
            else {
                // if child already visited it, calculate 
                // how much time passed since last visit
                auto now = std::chrono::system_clock::now();
                auto before = MmapManager::getInstance().getElement("www.google.com");
                std::chrono::duration<double> diff = now-before;
                std::cout << "PID " << getpid() << " visited www.google.com " << diff.count() << " seconds ago" << std::endl;
            }
            MmapManager::getInstance().showMap();
            _exit(EXIT_SUCCESS);
        }
    }
    return 0;
}

这是可能的输出之一:

PID 12457 www.google.com is new
PID 12459 www.google.com is new
PID 12458 www.google.com is new
PID 12460 www.google.com is new
PID 12461 www.google.com is new

我不能使用其他外部库,如 Boost 或使用线程:我知道它们共享内存,但程序是这样设计的(子进程做事),我不能修改它(原始代码不是我的)。

为什么每个进程仍然看到自己的地图副本?

编辑:我想我做了你建议我做的所有事情:

  • map 中的插入受到锁定机制的保护(感谢 kfsone);
  • stringmap 创建了一个自定义分配器(感谢Maxim Egorushkin 为这两个分配器);
  • map 在分叉之前分配(感谢 Zan Lynx)。

输出没有不同,map 仍然没有共享:

MmapManager()
printMap
map empty
PID 5085 www.google.com is new
PID 5086 www.google.com is new
PROC 5086 added www.goole.com
PROC 5085 added www.goole.com
PID 5087 www.google.com is new
PROC 5087 added www.goole.com

您建议我使用 Boost,但我想在我的代码运行后使用它:我不是在重新发明轮子,只是在艰难地学习。

下面是我的新代码:

#include <sys/mman.h>
#include <unistd.h>
#include <sys/shm.h>        /* shmat(), IPC_RMID        */
#include <semaphore.h>      /* sem_open(), sem_destroy(), sem_wait().. */
#include <fcntl.h>          /* O_CREAT, O_EXEC          */
#include <stdlib.h>

#include <iostream>
#include <map>
#include <string>
#include <chrono>
#include <cstddef>
#include <vector>
#include <limits>
#include <memory>

template<typename T> class stringAllocator {
    public : 
        typedef T value_type;
        typedef size_t size_type;
        typedef ptrdiff_t difference_type;
        typedef T* pointer;
        typedef T const * const_pointer;
        typedef T& reference;
        typedef T const & const_reference;

        template<typename U> struct rebind {
            typedef stringAllocator<U> other;
        };

        pointer address (reference value ) const {
            return &value;
        }

        const_pointer address (const_reference value) const {
            return &value;
        }

        size_type max_size () const throw() {
            return std::numeric_limits <size_type>::max() / sizeof(T);
        }

        stringAllocator () throw () {}
        stringAllocator (stringAllocator const &) throw () {}

        template <typename U>
        stringAllocator(stringAllocator <U> const &) throw () {}

        ~stringAllocator() throw () {}

        pointer allocate (size_type n) {
            pointer ptr = (pointer)malloc(n * sizeof(value_type));
            return ptr;
        }

        void deallocate (pointer p, size_type n) {
            free(p);
        }

        void construct (pointer p, const_reference value) {
            new(p) T(value);
        }

        void destroy (pointer p) {
            p->~T();
        }
};

template <class T1, class T2>
    bool operator==(const stringAllocator<T1>&, const stringAllocator<T2>&) throw() {
        return true;
    }

template <class T1, class T2>
    bool operator!=(const stringAllocator<T1>&, const stringAllocator<T2>&) throw() {
        return false;
    }

typedef std::basic_string<
    char, 
    std::char_traits<char>, 
    stringAllocator<char>
> myString;

/*************************************** map allocator ****************************************/

template<typename T> class mapAllocator{
    public :
        typedef T value_type;
        typedef value_type* pointer;
        typedef const value_type* const_pointer;
        typedef value_type& reference;
        typedef const value_type& const_reference;
        typedef std::size_t size_type;
        typedef std::ptrdiff_t difference_type;

        template<typename U>
        struct rebind {
            typedef mapAllocator<U> other;
        };

        mapAllocator() throw() {}
        mapAllocator (mapAllocator const &) throw () {} 
        ~mapAllocator() throw () {}

        template<typename U>
        mapAllocator(mapAllocator<U> const&) {}

        pointer address(reference r) { return &r; }
        const_pointer address(const_reference r) { return &r; }

        pointer allocate(size_type cnt, typename std::allocator<void>::const_pointer = 0) {
            pointer new_memory = reinterpret_cast<pointer>(::operator new(cnt * sizeof (T)));
            return new_memory;
        }
        void deallocate(pointer p, size_type n) {
            ::operator delete(p);
        }
        size_type max_size() const {
            return std::numeric_limits<size_type>::max() / sizeof(T);
        }

        void construct(pointer p, const T& t) {
            new(p) T(t);
        }

        void destroy(pointer p) {
            p->~T();
        }    
};

template <class T1, class T2>
    bool operator==(const mapAllocator<T1>&, const mapAllocator<T2>&) throw() {
        return true;
    }

template <class T1, class T2>
    bool operator!=(const mapAllocator<T1>&, const mapAllocator<T2>&) throw() {
        return false;
    }

/*************************************** end map allocator ****************************************/

// class compare for map with std::string as Key
class strless {
    public:
        bool operator() (const myString first, const myString second ) const { 
            return first.compare(second) < 0; 
        }
};

template<typename Key, typename T>
    using Map = std::map<
        Key,                                    // class Key
        T,                                      // class T
        strless,                                // class Compare = std::less<Key>
        mapAllocator<std::pair<const Key, T>    // class Allocator = std::allocator<std::pair<const Key, T> >
    >
>;

// typedef for the actual map I need to share between processes
typedef Map<myString, std::chrono::system_clock::time_point> frequencyMap;

class MmapManager {
    private:
        MmapManager() {
            std::cout << "MmapManager()" << std::endl;
            semMmap = sem_open("semaphore", O_CREAT|O_EXCL, 0644, 1);
            sem_unlink("semaphore");
        };

        ~MmapManager() {
            std::cout << "~MmapManager()" << std::endl;
        }

    public:
        static MmapManager& getInstance() {
            static MmapManager instance;
            return instance;
        }

    private:
        frequencyMap fmap;
        sem_t *semMmap;

    public:

        void start(void) {}

        bool isHostAlreadyVisited(myString host) {
            return fmap.find(host) != fmap.end();
        }

        void addHost(myString host) {
            sem_wait(semMmap);
            fmap[host] = std::chrono::system_clock::now();
            sem_post(semMmap);
            std::cout << "PROC " << getpid() << " added " << host << std::endl;
        }

        // get time of the visit for site "host"
        std::chrono::system_clock::time_point getElement(myString host) {
            return fmap[host];
        }

        void printMap(void) {
            std::cout << "printMap" << std::endl;
            if (!fmap.empty()) {
                for (auto it : fmap) {
                    std::cout << it.first << ' ';
                }
                std::cout << std::endl;
            } else {
                std::cout << "map empty" << std::endl;
            }
        }
};


int main(void) {

    MmapManager::getInstance().start();

    for (int i=0; i<3; i++) {
        if (fork() == 0) {
            if (!MmapManager::getInstance().isHostAlreadyVisited("www.google.com")) {
                std::cout << "PID " << getpid() << " www.google.com is new" << std::endl;           
                MmapManager::getInstance().addHost("www.goole.com");
            }
            else {
                // if child already visited it, calculate
                // how much time passed since last visit
                auto now = std::chrono::system_clock::now();
                auto before = MmapManager::getInstance().getElement("www.google.com");
                std::chrono::duration<double> diff = now-before;
                std::cout << "PID " << getpid() << " visited www.google.com " << diff.count() << " seconds ago" << std::endl;
            }
            _exit(EXIT_SUCCESS);
        }
    }

    MmapManager::getInstance().printMap();

    return 0;
}

【问题讨论】:

  • 看看这个有用的东西:stackoverflow.com/questions/12413034/…
  • 看看 boost::interprocess 可以做的所有事情。
  • 我想在没有提升的情况下这样做;一旦学会,我会开始使用它:)
  • std::map 不是 thread 安全的,更别提进程安全了。您可能希望在此处对访问进行一些锁定。
  • 确实,我正在考虑使用sem_t * semMap = sem_open("mmapSem", O_CREAT|O_EXCL, 0644, 1); sem_unlink("mmapSem");;每个进程使用sem_wait(semMap) 访问std::map,然后在完成其工作后访问sem_post(semMap)

标签: linux c++11 shared-memory mmap


【解决方案1】:

这不起作用,因为尽管您将容器对象放入共享内存中,但元素仍然从堆中分配,因此其他进程无法访问它们。

您需要一个在共享内存中分配元素的自定义分配器。请参阅Creating maps in shared memory 了解它是如何完成的。

请注意,您使用的字符串类也必须从共享内存中分配内存。

换句话说,你不能在共享内存中拥有指向堆内存的指针,因为堆内存不是在进程之间共享的。 std 类有一个分配器模板参数,默认从堆分配内存。这需要更改为 shared memory allocator 才能通过共享内存共享此类对象。

【讨论】:

  • 仅此而已?谢谢好心的先生,当我完成这项工作时,我会用一个工作示例来编辑我的问题。
  • 呃,关于你的最后一句话:如果我理解得很好,我也需要一个用于std::string 的自定义分配器?
  • @elmazzun 是的,你知道。您不能在共享内存中拥有指向堆分配的指针,因为堆不在进程之间共享。
  • 我正在查看 [this] (ideone.com/wBtaks) 用于 map 的自定义分配器,但很简单,std::map&lt;int,int&gt;:所以,连同我的自定义 map 分配器,我'必须提供一个std::string 自定义分配器。至于mapstd::chrono::system_clock::time_point&gt;的值,不需要自定义分配器吗?
  • std::chrono::system_clock::time_point&gt; 不分配堆内存,因此可以轻松在进程之间传递。
【解决方案2】:

您的代码不起作用的另一个原因是您仅在调用fork() 之后创建地图

如果您希望所有孩子都能看到您的MAP_SHARED|MAP_ANONYMOUS 地图,那么您必须在分叉之前调用mmap()

【讨论】:

    猜你喜欢
    • 2011-06-26
    • 1970-01-01
    • 1970-01-01
    • 2019-11-28
    • 1970-01-01
    • 2012-07-22
    • 1970-01-01
    • 1970-01-01
    • 2019-10-13
    相关资源
    最近更新 更多