На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
[!] Как относитесь к модерированию на этом форуме? Выскажите свое мнение здесь
Модераторы: Qraizer
Страницы: (2) [1] 2  все  ( Перейти к последнему сообщению )  
> Помогите подобрать правильный шаблон проектирования, , который позволяет порождать много инстансов одного класса.
    Всем привет.
    Есть вот такой набросок псевдокода:
    ExpandedWrap disabled
      class IModuleUpgrader
      {
         virtual void SetUpgradeCode() = 0;
         virtual void SetVersion() = 0;
         virtual void UpgradeModule(string modulename) = 0;
      }
       
      struct IsmUpdater : public IModuleUpgrader
      {
        void UpgradeModule(string modulename) override
        {
            parser.ProcessUpgrade();
            parser.Save();
        }
        void SetUpgradeCode() override {}
        void SetVersion() override {}
      private:
        XMLParser parser;
      };
       
      struct WixUpdater : public IModuleUpgrader
      {
        void UpgradeModule(string modulename) override
        {
            parser.ProcessUpgrade();
            parser.Save();
        }
        void SetUpgradeCode() override {}
        void SetVersion() override {}
      private:
        XMLParser parser;
      };
       
      ...
       
      struct UpgradeTool
      {
          void AddUpgradeTool(const std::wstring& ext, std::unique_ptr<IModuleUpgrader>&& upgrader)
          {
             m_scaner.AddFilter(ext);
             m_upgraders.insert(make_pair(ext,std::move(upgrader)));
          }
          
          //! Producer выполняется в отдельном потоке
          void Scann()
          {
              m_scanner.ScannAndPutResultToContainer(m_container);
          }
          
          //! Consummer может выполнятся в отдельных разных потоках
          void Upgrade()
          {
             while(true)
             {
                auto module = m_container.pop();
                auto ext = GetModuleType(module);
                if(m_upgraders.has(ext))
                {
                   auto upgrader = m_upgraders[ext];
                   upgrader->SetUpgradeCode(...);
                   upgrader->SetVersion(...);
                   upgrader->Upgrade();
                }
             }
          }
          private:
              FileScanner m_scanner;
              ThreadSafeQueue<std::wstring> m_container;
              std::map<std::wstring, std::unique_ptr<IModuleUpgrader>> m_upgraders;
      }

    По задумке мы пихаем расширения нужных нам файлов в класс UpgradeTool, и класс, который может их обрабатывать.
    Дальше запускается отдельный поток, в котором ищутся все файлы с заданным расширением и файлы эти кладутся в отдельную потокобезопасную очередь, потом запускается несколько отдельных потоков, и каждый отдельный поток начинает обрабатывать свой файл.
    Но тут есть косяк: Если я вызову AddUpgradeTool(".ext", new IsmUpdater );
    То у меня будет 1 объект класса IsmUpdater, а мне нужно чтоб было столько, сколько потоков, но я не могу понять как это сделать.
    Вот например приходила такая идея:
    ExpandedWrap disabled
          template<typename _Type = IModuleUpgrader>
          struct ProxyModuleUpgrader
          {
              using TModuleUpgrader = _Type;
          };
          std::map<std::wstring, std::unique_ptr<ProxyModuleUpgrader<>>> m_upgraders;
      //! Где то :
              upgrade.AddUpgradeTool<IsmUpdater>(L".ext", new IsmUpdater)
       
      //! Внутри апгрейда
           void UpgradeTool::Upgrade()
          {
             while(true)
             {
                    auto module = m_container.pop();
                auto ext = GetModuleType(module);
                if(m_upgraders.has(ext))
                {
                     auto upgrader = m_upgraders[ext]::TModuleUpgrader;
                   upgrader->SetUpgradeCode(...);
                   upgrader->SetVersion(...);
                   upgrader->Upgrade();
                }
             }
          }

    Но эта идея тоже провалилась.
    Может быть есть какой то шаблон или прием для решения такой задачи? Я что то не могу сообразить как же так лучше сделать то :unsure:
      Цитата Wound @
      как же так лучше сделать то

      А задача-то какая? Многопоточная обработка файлов по расширениям и/или по группам расширений?
        Цитата JoeUser @
        Многопоточная обработка файлов по расширениям и/или по группам расширений?

        В одном потоке запускается хрень(FileScanner), которая ищет все файлы в директории и поддиректориях по указанным расширениям, и пихается путь к найденному файлу в очередь, а потом запускаются несколько потоков, которые должны эту самую очередь выгребать и обновлять каждый файл, в зависимости от типа файла. Один файл может обновлятся достаточно долгое время(от секунды до минуты например).
        Для каждого потока должен создаваться отдельный объект класса, который умеет работать с файлом данного типа.
        Т.е. один класс на несколько потоков - не варик вообще.
        Сообщение отредактировано: Wound -
          Цитата Wound @
          Вот например приходила такая идея:

              template<typename _Type = IModuleUpgrader>
              struct ProxyModuleUpgrader
              {
                  using TModuleUpgrader = _Type;
              };
              std::map<std::wstring, std::unique_ptr<ProxyModuleUpgrader<>>> m_upgraders;
          //! Где то :
                  upgrade.AddUpgradeTool<IsmUpdater>(L".ext", new IsmUpdater)
           


          Можно сделать например так
          ExpandedWrap disabled
            std::map<std::wstring, std::function<std::unique_ptr<IModuleUpgrader>()>> upgraders;
            .....
            upgraders.emplace(L".ext", []{return std::make_unique<IsmUpdater>()});
            upgraders.emplace(L".ext2", []{return std::make_unique<WixUpdater>()});
            ....
             
            auto it = upgraders.find(ext);
            if (it == upgraders.end())
              return;
             
            auto upgrader = it->second();
            Да примерно так и сделал, только добавил в интерфейс IModuleUpgrader новый чистовиртуальный метод -> virtual std::unique_ptr<IModuleUpgrader> CreateNewInstance() const = 0;
            А уж потомки реализуют его как у тебя в лямбде.
            Хотя может твой способ и лучше, не нужно писать лишнего метода в интерфейсе.
            Спасибо.

            Добавлено
            Вопрос решен.
              Цитата Wound @
              Да примерно так и сделал, только добавил в интерфейс IModuleUpgrader новый чистовиртуальный метод -> virtual std::unique_ptr<IModuleUpgrader> CreateNewInstance() const = 0;
              А уж потомки реализуют его как у тебя в лямбде.


              Его точно не реализуют. Чтобы вызвать виртуальный метод в интерфейсе, нужно сначала создать экземплляр кдасса который реализует этот интерфейс. Он будет через этот метод копировать себя, и больше ничего не делать, вряд ли ты станешь вызывать у него другие методы. Зачем для этого держать отдельный объект? Достаточно указателя на функцию или std::function.
                Цитата Олег М @
                Его точно не реализуют. Чтобы вызвать виртуальный метод в интерфейсе, нужно сначала создать экземплляр кдасса который реализует этот интерфейс. Он будет через этот метод копировать себя, и больше ничего не делать, вряд ли ты станешь вызывать у него другие методы. Зачем для этого держать отдельный объект? Достаточно указателя на функцию или std::function.

                Фишка в том, что интерфейс там появился для того, чтобы внутри void Upgrade() вызвать соответствующие методы:
                ExpandedWrap disabled
                  void Upgrade()
                      {
                         while(true)
                         {
                            auto module = m_container.pop();
                            auto ext = GetModuleType(module);
                            if(m_upgraders.has(ext))
                            {
                               auto upgrader = m_upgraders[ext]; <------ Вот тут вместо auto должна быть копия объекта IsmUpdater или WixUpdater или еще чего то.
                               upgrader->SetUpgradeCode(...);
                               upgrader->SetVersion(...);
                               upgrader->Upgrade();
                            }
                         }
                      }

                Для этого я создал интерфейс, и добавил функцию регистрации:
                ExpandedWrap disabled
                      void UpgradeTool::AddUpgradeTool(std::wstring ext, std::unique_ptr<Msi::IModuleUpgrader>&& upgrader)
                      {
                          m_scanner.AddExtension(ext);
                          m_upgraders.insert(std::make_pair(std::move(ext), std::move(upgrader)));
                      }

                Теперь в классах IsmUpdater и WixUpdater реализовал чистовиртуальную функцию интерфейса IModuleUpgrader:
                ExpandedWrap disabled
                      std::unique_ptr<IModuleUpgrader> ISMFileUpdater::CreateNewInstance() const
                      {
                          std::cout << "ISMFileUpdater" << std::endl;
                          return std::make_unique<ISMFileUpdater>();
                      }

                Теперь в функцию потока void Upgrade() я могу написать так:
                ExpandedWrap disabled
                  void Upgrade()
                      {
                         while(true)
                         {
                            auto module = m_container.pop();
                            auto ext = GetModuleType(module);
                            if(m_upgraders.has(ext))
                            {
                               //auto upgrader = m_upgraders[ext]; <------ Вот тут вместо auto должна быть копия объекта IsmUpdater или WixUpdater или еще чего то.
                           std::unique_ptr<Msi::IModuleUpgrader> ptr = m_upgraders[ext];
                           auto upgrader = ptr->CreateNewInstance();  
                           upgrader->LoadModule(L"test.ism");
                               upgrader->SetUpgradeCode(...);
                               upgrader->SetVersion(...);
                               upgrader->Upgrade();
                            }
                         }
                      }

                А регистрировать свои классы вот так:
                ExpandedWrap disabled
                      UpgradeTool tool;
                      tool.AddUpgradeTool(L".ism", std::make_unique<Msi::ISMFileUpdater>());


                Это так сейчас сделано, если делать как ты написал с указателем на функцию, то ведь придется в функцию регистрации передавать лямбда выражение целое?
                Что то типа:
                ExpandedWrap disabled
                      UpgradeTool tool;
                      tool.AddUpgradeTool(L".ism", []{return std::make_unique<IsmUpdater>()});


                Добавлено
                Ну в принципе я так и переписал. Просто меня смущает что нужно передавать лямбду в параметр функции. Слишком запутанно выходит. Хотя может и нет.
                Сообщение отредактировано: Wound -
                  Wound, честно говоря, я бы начал немного раньше планировать разработку. А именно с анализа предметной области. Хотя бы с одного вопроса "какие ресурсы компа в потенциале в процессе обработки будут более востребованы?". Если ответ "а хрен его спрогнозирует" - тогда выбираем "классику". А именно, число потоков-обработчиков = N+1, где N-количество ядер проца. А вот, к примеру, если дисковых операций тьма, а математика не только лишь все имена файлов обрабатывает, мало какие данные в файлах обрабатываются (L) :lol: Тут свое построение нужно делать.

                  Одно понятно, сущностей четыре:

                  • Обработчик списка файлов
                  • Хранитель стека необработанных файлов (а весьма возможно или вектора, или ваще мапы!)
                  • Пулл обработчиков
                  • Очередь обработки

                  Две очевидных стратегии:

                  • Наванговали, создали заранее пул обработчиков с нехилым запасом, создали фиксированную очередь обработки, определили строгую стратегию выборки на обработку
                  • Решили, пусть прога сама считает что и как создавать на основе статистики уже обработанного. Тут уже придется не полениться. Поискать критерии (расширение, длина файла, дата создания) и корреляция этой хрени с другими параметрами - общем времени обработки, обработки файлов определенного расширения, средними значениями обработки по расширениям (длинам, датам)

                  Ну и конечно же основной вопрос - если прога одноразовая, используем стратегию намба уан и не паримся ни разу.
                  Все выше - мое ИМХО.

                  В качестве бонуса - архивчик, в котором есть полезные книжки по твоему топику, может быть поможет :) :

                  • Параллельное и распределенное программирование на C++.Хьюз.djvu
                  • Параллельное программирование на С++ в действии.Уильямс.djvu
                  • Приемы объектно-ориентированного проектирования. Паттерны проектирования, 2001.Гамма.djvu
                    Цитата JoeUser @
                    Wound, честно говоря, я бы начал немного раньше планировать разработку. А именно с анализа предметной области. Хотя бы с одного вопроса "какие ресурсы компа в потенциале в процессе обработки будут более востребованы?". Если ответ "а хрен его спрогнозирует" - тогда выбираем "классику". А именно, число потоков-обработчиков = N+1, где N-количество ядер проца. А вот, к примеру, если дисковых операций тьма, а математика не только лишь все имена файлов обрабатывает, мало какие данные в файлах обрабатываются (L) Тут свое построение нужно делать.

                    Да ресурсов не много нужно. Есть куча инсталяционных файлов Installshield и Wix(*.ism, *.wix), они представляют из себя XML, в них хранится вся информация, из которой потом компилируются инсталяционные пакеты Setup.exe/*.cab.
                    Инсталяционных файлов много, ну штук под 30 примерно, и есть куча разных версий, которым никто не настраивал Major upgrade, А чтоб Major Upgrade работал, нужно в каждом файле сгенерировать новый ProductCode, потом нужно задать ProductVersion, И прописать все это в таблицу Upgrade - выставить нужные версии. Руками все это делать муторно и долго, и я задолбался. В итоге решил автоматизировать все это дело. Чтобы прога сама искала все нужные файлы и сама апгрейдила все что нужно во время компиляции, тогда даже когда сделают новую ветку, мне не нужно будет там настраивать Major Upgrade.
                    В итоге я написал XML парсер, который загружает только нужные для апгрейда части, чтобы жрало поменьше памяти, написал класс который апгрейдид все это, и хочу теперь воссоединить это в одно. Так как файлов много, решил что каждый файл будет апгрейдится в отдельном потоке. Сколько для этого нужно потоков пока не знаю, но думаю юзать фьючерсы, там вроде они умные и сами умеют распределять ресуосы системы. Так что конкретно число потоков мне пока не важна. У меня проблема была в том, чтобы в отдельном потоке была отдельная копия класса-апгрейдера. Но вроде уже решилась.
                      Wound, Киля, самый главный вопрос - задача разовая или перманентная по времени?

                      Добавлено
                      Цитата Wound @
                      фьючерсы, там вроде они умные и сами умеют распределять ресуосы системы

                      Это заблуждение! Распределять ресурсы умеет ОС. Программер может помочь, зная специфику обработки.
                        Цитата JoeUser @
                        Wound, Киля, самый главный вопрос - задача разовая или перманентная по времени?

                        Нет, она не одноразовая, она по сути много разовая и умеет работать с любыми инсталяционными файлами Installshield.


                        Цитата JoeUser @
                        Это заблуждение! Распределять ресурсы умеет ОС. Программер может помочь, зная специфику обработки.

                        Ну у Мейерса как раз и написано чтобы отдавали предпочтение фьючерсам, так как они шибко умные и сами знают нужно ли порождать поток или исполнять в текущем, а так же все тонкости балансировщика они тоже знают.
                          Цитата Wound @
                          Цитата JoeUser @
                          Wound, Киля, самый главный вопрос - задача разовая или перманентная по времени?

                          Нет, она не одноразовая, она по сути много разовая и умеет работать с любыми инсталяционными файлами Installshield.

                          Тогда, действительно, задача интересна и стоит усилий!

                          Цитата Wound @
                          Цитата JoeUser @
                          Это заблуждение! Распределять ресурсы умеет ОС. Программер может помочь, зная специфику обработки.
                          Ну у Мейерса как раз и написано чтобы отдавали предпочтение фьючерсам, так как они шибко умные и сами знают нужно ли порождать поток или исполнять в текущем, а так же все тонкости балансировщика они тоже знают.

                          Философское отступление. Несколько лет назад у меня спала "пелена из глаз". До этого, в течении многого времени, я считал себя заядлым агностиком. Многие вааще не понимали о чем речь. Но пришло осознание - я не агностик или атеист, я - неверующий. Даже религия тут не при чем! Я не верю ваще, ни во что. Полностью. Я пытаюсь воспроизвести и оценить. Гадил я с Пизанской башни на аксиомы "аксакалов". Но ... приду и проверю :lol:

                          ... это я кидаю камень в сторону Меерса :lol:

                          Есть "логика", есть высказывания Меерса, есть куча методик шкальных оценок достоверности. Ну и есть чуйка))) Поверил во "фьючерсы" - это не смертельно! Возьми вычлени репрезентативную выборку результатов синтетических тестов на своих данных - и ты поймешь. Нужно ли слепо идти за Мейерсом, или эффективнее просто прислушиваться, приглядоваться, записывать ходы... E2-E4?
                            Цитата JoeUser @
                            Есть "логика", есть высказывания Меерса, есть куча методик шкальных оценок достоверности. Ну и есть чуйка))) Поверил во "фьючерсы" - это не смертельно! Возьми вычлени репрезентативную выборку результатов синтетических тестов на своих данных - и ты поймешь. Нужно ли слепо идти за Мейерсом, или эффективнее просто прислушиваться, приглядоваться, записывать ходы... E2-E4?

                            Дело в том, что фьючерсы дают чуть больше функционала, чем потоки. Например, если внутри потока сгенерируется исключение, то ты его просто не словишь, с фьючерсами - легко обработать этот вариант, так же можно получить возвращаемое потоком значение.
                            Ну и вывод, взято у Майерса:
                            Цитата
                            Следует запомнить

                            API std::thrеаd не предлагает способа непосредственного получения
                            возвращаемых значений из асинхронно выполняемых функций, и, если такие
                            функции генерируют исключения, программа завершается.

                            Программирование на основе потоков требует управления вручную исчерпанием
                            потоков, превышением подписки, балансом загрузки и адаптацией к новым
                            платформам.
                            • Программирование на основе задач с помощью std : : async со стратегией
                            запуска по умолчанию решает большинство перечисленных проблем вместо вас.

                            Вообще у него вроде понятно расписано, чтобы знать что как работает, а не верить :-?
                            Сообщение отредактировано: Wound -
                              Цитата Wound @
                              Вообще у него вроде понятно расписано, чтобы знать что как работает, а не верить :-?

                              Вот и я говорю - написано одно, а так ли это на самом деле? Я понимаю, когда в реализацию компилятора/линкера вводят особенности архитектуры. Но вот на автомате предусмотреть особенности конкретной модели проца - это совсем другое.
                                Цитата JoeUser @
                                Вот и я говорю - написано одно, а так ли это на самом деле? Я понимаю, когда в реализацию компилятора/линкера вводят особенности архитектуры. Но вот на автомате предусмотреть особенности конкретной модели проца - это совсем другое.

                                При прочих равных фьючерсы более предпочитетельнее, чем потоки. А пилить какие то узкие места в многопоточности под конкретные модели процессора у меня задачи не стоит.
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0620 ]   [ 17 queries used ]   [ Generated: 16.04.24, 16:51 GMT ]