Синглтон в общей библиотеке в Linux не работает должным - PullRequest
2 голосов
/ 27 марта 2019

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

Это код совместно используемой библиотеки:

singleton.h

#ifndef SINGLETON_H
#define SINGLETON_H

#if defined _WIN32 || defined __CYGWIN__
  #ifdef BUILDING_DLL
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllexport))
    #else
      #define DLL_PUBLIC __declspec(dllexport) // Note: actually gcc seems to also supports this syntax.
    #endif
  #else
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllimport))
    #else
      #define DLL_PUBLIC __declspec(dllimport) // Note: actually gcc seems to also supports this syntax.
    #endif
  #endif
  #define DLL_LOCAL
#else
  #if __GNUC__ >= 4
    #define DLL_PUBLIC __attribute__ ((visibility ("default")))
    #define DLL_LOCAL  __attribute__ ((visibility ("hidden")))
  #else
    #define DLL_PUBLIC
    #define DLL_LOCAL
  #endif
#endif

class DLL_PUBLIC Singleton
{
public:   
    static Singleton &instance();
    int test();

private:
    Singleton();
    int m_num;
};

#endif

singleton.cpp

#include "singleton.h"

Singleton &Singleton::instance()
{
    static Singleton singleton;
    return singleton;
}

Singleton::Singleton() :
    m_num(0)
{ }

int Singleton::test()
{
  return ++m_num;
}

компилируется и связывается следующим образом:

g++ -c -pipe -fPIC -o singleton.o singleton.cpp
g++ -rdynamic -export-dynamic -shared -Wl,-soname,libSingletonLib.so.1 -o libSingletonLib.so.1.0.0 singleton.o

Небольшая утилита тестирования:

main.cpp

#include <stdio.h>
#include "singleton.h"

int main(int argc, char *argv[])
{    
    int num = Singleton::instance().test();
    printf("num: %d", num);
    getchar();

    return 0;
}

и опции компиляции и связывания:

g++ -c -pipe -g -std=gnu++11 -Wall -W -fPIC -o main.o main.cpp
g++ -Wl -o ../SingletonTest main.o   -L.. -lSingletonLib 

Теперь я запускаю 2 экземпляра тестового приложения и ожидаюоба они будут использовать синглтон, и число будет увеличиваться.Но неожиданно получается:

первый экземпляр:

./SingletonTest 
num: 1

второй экземпляр:

./SingletonTest
num: 1

Примечание:первое приложение все еще работает, когда запускается второе.

Как я понимаю, каждый экземпляр приложения создает разные экземпляры синглтона.

Как можно избежать этой ситуации, чтобы все экземпляры, которые были связаны с одной и той же общей библиотекой, использовали только один синглтон?

Я использую:

  • Ubuntu 18.04

  • gcc (Ubuntu 7.3.0-27ubuntu1 ~ 18.04) 7.3.0

Обновление: Хорошо, похоже, мне нужно использовать общую память или какую-то межпроцессную связь.Я никогда не работал с этим раньше, поэтому я перефразирую вопрос: как я могу использовать один и только один синглтон для нескольких процессов?Как я могу поместить его в общую память или что-то еще?

1 Ответ

0 голосов
/ 27 марта 2019

Хорошо, после долгих поисков, я думаю, я нашел решение (или, возможно, обходной путь). По какой-то причине я полностью упустил из виду, что разделяемая библиотека разделяет код, но не разделяет данные. Каждое приложение, которое использует одну и ту же общую библиотеку, создает свою копию синглтона. Так что я понимаю, что мне нужно использовать IPC, как предложено в комментариях (спасибо @ Gojita и @Mayur).

Это код, к которому я пришел:

#include <new>
#include <errno.h>

...

Singleton &Singleton::instance()
{
    //static Singleton instance;
    //return instance;
    static Singleton* instance = getSharedMemory();
    return *instance;
}

Singleton * Singleton::getSharedMemory()
{
    Singleton * instance = nullptr;
    bool already_exists = false;

    int shm = shm_open("my_memory", O_CREAT | O_RDWR | O_EXCL, 0777);
    if(shm == -1)
    {
        if(errno == EEXIST)
        {
            already_exists = true;
            shm = shm_open("my_memory", O_CREAT | O_RDWR , 0777);
        }
        if(shm == -1)
        {
            perror("shm_open error");
            return nullptr;
        }
    }

    void *addr = mmap(nullptr, sizeof(Singleton) + 1, PROT_WRITE | PROT_READ, MAP_SHARED, shm, 0);
    if(!already_exists)
    {
        if (ftruncate(shm, sizeof(Singleton) + 1) == -1)
        {
            perror("ftruncate error");
            return nullptr;
        }
        instance = new(addr) Singleton;
    }
    else
        instance = reinterpret_cast<Singleton *>(addr);


    return instance;
}

примечания: в приведенном ниже коде я создаю область общей памяти (с именем "my_memory"). Если память уже выделена (флаг O_EXCL выполняет свою работу), я просто использую ее, в противном случае я устанавливаю ее размер (используя ftruncate) и создаю свой синглтон, размещая новое в памяти. В качестве бонуса - вы можете выйти из приложения, затем ввести его снова и использовать синглтон как есть - класс все еще остается в памяти. Память можно освободить, вызвав munmap и shm_unlink или перезагрузив ОС.

...