2016-08-26 12 views
0

Я пытаюсь разделить std::map<std::string, std::chrono::system_clock::time_point> map: каждая строка является именем хоста, определяющим сайт, а time_point - это последний раз, когда процесс посетил этот сайт.Поделиться std :: map между процессами с mmap()

Я пытался с 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 или использовать потоки: я знаю, что они обмениваются памятью, но программа была разработана таким образом (с дочерними процессами, делающими вещи), и я не могу ее модифицировать (оригинальный код не мой).

Почему каждый процесс по-прежнему видит свою собственную копию карты?

Edit: Я думаю, что я сделал все, что вы предложили мне:

  • вставки в map защищен с механизмом блокировки (спасибо kfsone);
  • создал специальный распределитель для string, а другой для map (спасибо Максиму Егорушкину за эти два);
  • 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 

Вы предложили мне использовать подталкивание, но я хотел бы использовать его после того, как мой код будет работать: я не изобретать колесо, просто изучая трудный путь.

Далее следует мой новый код:

#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; 
} 
+0

Посмотрите на что-нибудь полезное: http://stackoverflow.com/questions/12413034/shared-map-with-boostinterprocess –

+0

И проверьте все остальное boost :: interprocess. –

+0

Я хочу сделать это без повышения; как только я узнал, я начну использовать его :) – elmazzun

ответ

2

Это не работает, потому что, хотя вы поместили объект контейнера в общую память, элементы по-прежнему выделяется из кучи, и, таким образом, они не доступны другими способами ,

Вам нужен пользовательский распределитель, который выделяет элементы в общей памяти. См. Creating maps in shared memory о том, как это делается.

Обратите внимание, что класс строк, который вы используете, также должен выделять память из общей памяти.

Другими словами, вы не можете иметь указатели на кучу памяти в общей памяти, так как куча памяти не разделяется между процессами. std классы имеют аргумент шаблона распределителя, по умолчанию выделяется память из кучи. Это необходимо изменить на shared memory allocator, чтобы иметь возможность делиться такими объектами через общую память.

+0

Это все, что нужно? Спасибо, сэр, я отредактирую свой вопрос с рабочего примера, когда я сделаю эту работу. – elmazzun

+0

О, ваше последнее предложение: если я хорошо понял, мне нужен специальный распределитель для 'std :: string' тоже? – elmazzun

+0

@elmazzun Да, да. У вас нет указателей на распределение кучи в общей памяти, так как куча не разделяется между процессами. –

1

Другая причина, по которой ваш код не работает, заключается в том, что вы создаете карты после, которых вы назвали fork().

Если вы хотите, чтобы ваш MAP_SHARED|MAP_ANONYMOUS карту, чтобы увидеть все дети, то вы должны вызвать mmap()перед тем разветвление.