От новичка до гуру: Курсы программирования на CyberDuff

Обходные решения для конкретных платформ для статических проблем с уничтожением/строительством C++

Я разрабатываю Visual Studio 2008 в стандартном (неуправляемом) C++ под Windows XP Pro SP 3.

Я создал потокобезопасную оболочку вокруг std::cout. Этот объект-оболочка является заменой (то есть с тем же именем) того, что используется в качестве макроса, который был #defined для cout. Он используется в множестве кода. Его поведение, вероятно, в значительной степени соответствует вашим ожиданиям:

  1. При построении он создает критическую секцию.

  2. Во время вызова operator‹‹() он блокирует критическую секцию, передает данные для печати в cout и, наконец, освобождает критическую секцию.

  3. При разрушении разрушает критическую секцию.

Эта оболочка находится в статическом хранилище (глобально). Как и все подобные объекты, он создается до запуска функции main() и разрушается после выхода из функции main().

Использование моей оболочки в деструкторе другого объекта, который также находится в статическом хранилище, проблематично. Так как порядок строительства/уничтожения таких объектов не определен, я вполне могу попытаться заблокировать критическую секцию, которая была уничтожена. Симптом, который я вижу, заключается в том, что моя программа блокируется при попытке блокировки (хотя я полагаю, что может произойти что угодно).

Что касается способов борьбы с этим...

  1. Я ничего не мог сделать в деструкторе; в частности, я бы позволил критической секции продолжать жить. Стандарт C++ гарантирует, что cout никогда не умрет во время выполнения программы, и это будет наилучшей попыткой заставить мою оболочку вести себя аналогичным образом. Конечно, моя обертка будет «официально» мертва после запуска ее пустого деструктора, но, вероятно, (я ненавижу это слово) она будет так же функциональна, как и до запуска ее деструктора. На моей платформе это действительно так. Но, черт возьми, это уродливо, непереносимо и может сломаться в будущем...

  2. Я держу критическую секцию (но не ссылку потока на cout) в файле pimpl. Всем обращениям к критическим секциям через pimpl предшествует проверка на ненулевое значение pimpl. Так получилось, что я забыл установить pimpl в 0 после вызова удаления в деструкторе. Если бы я установил это значение равным 0 (что я должен сделать в любом случае), вызовы моей оболочки после ее уничтожения ничего не сделают с критической секцией, но все равно будут передавать данные для печати в cout. На моей платформе это тоже работает. Опять некрасиво...

  3. Я мог бы сказать своим товарищам по команде, чтобы они не использовали мою оболочку после выхода из main(). К сожалению, аэродинамика у этого была бы примерно такая же, как у танка.

ВОПРОСЫ:

* Вопрос 1 * Для случая 1, если я оставлю критическую секцию неповрежденной, произойдет утечка ресурсов критической секции в ОС. Будет ли эта утечка сохраняться после полного выхода из моей программы? Если нет, случай 1 становится более жизнеспособным.

* Вопрос 2 * Что касается случаев 1 и 2, кто-нибудь знает, могу ли я на моей конкретной платформе действительно безопасно продолжать использовать свою оболочку после запуска ее пустого деструктора? Похоже, я могу, но я хочу узнать, знает ли кто-нибудь что-нибудь определенное о том, как моя платформа ведет себя в этом случае...

*Вопрос 3* Мои предложения явно несовершенны, но действительно правильного решения я не вижу. Кто-нибудь знает правильное решение этой проблемы?

Примечание: конечно, может возникнуть обратная проблема, если я попытаюсь использовать свою оболочку в конструкторе другого объекта, который также находится в статическом хранилище. В этом случае я могу попытаться заблокировать еще не созданный критический раздел. Я хотел бы использовать идиому «построение при первом использовании», чтобы исправить это, но это влечет за собой синтаксическое изменение всего кода, который использует мою оболочку. Это потребовало бы отказа от естественности использования оператора ‹‹. И в любом случае слишком много кода, который нужно изменить. Так что это не рабочий вариант. Я не очень далеко продвинулся в мыслительном процессе по этой половине проблемы, но я задам один вопрос, который может быть частью другого несовершенного способа решения проблемы...

* Вопрос 4 * Как я уже сказал, моя обертка живет в статическом хранилище (оно глобальное) и у нее есть прыщ (гормональная проблема :) ). У меня сложилось впечатление, что необработанные байты переменной в статическом хранилище устанавливаются равными 0 во время загрузки (если они не инициализированы по-другому в коде). Это будет означать, что pimpl моей оболочки имеет значение 0 до создания моей оболочки. Это правильно?

Спасибо, Дэйв

03.02.2012

  • Вы не первый, кто задумался над этой проблемой. В сантехнику iostream уже встроен низкоуровневый замок. Добавлять свои нет смысла. 03.02.2012
  • Я не читал всего этого, но звучит как это может быть вам полезно. По сути, вы можете возиться со статическим порядком инициализации. 03.02.2012
  • Конечно, стандарт C++ не гарантирует, что cout является потокобезопасным, но звучит так, как будто вы (Ганс) хотите сказать, что конкретная реализация C++ в Visual C++ действительно делает cout потокобезопасным. Я правильно понимаю? 03.02.2012

Ответы:


1

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

Рассмотрим два потока, которые выполнили cout << "Hi" << endl;, блокировка каждой операции не исключает «HiHi\n\n» в качестве вывода, и все становится намного сложнее с манипуляторами, где один поток может изменить формат для следующего печатаемого значения, а другой поток может вызвать следующую запись, и в этом случае два формата будут неправильными.

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

Вместо того, чтобы создавать объекты как глобальные, создайте вспомогательный тип, который выполняет подсчет ссылок для количества экземпляров типа. Конструктор проверит, является ли объект первым создаваемым объектом своего типа, и инициализирует потокобезопасную оболочку. Последний объект, который будет уничтожен, уничтожит вашу оболочку. Следующая часть головоломки — создание глобальной статической переменной этого типа в заголовке, который, в свою очередь, включает заголовок iostreams. Последняя часть головоломки заключается в том, что ваши пользователи должны включать ваш заголовок вместо iostreams.

03.02.2012
Новые материалы

Представляем Narwhal Technologies (Nrwl)
6 декабря 2016 г. Маунтин-Вью, Калифорния С тех пор, как Виктор Савкин и я (Джефф Кросс) присоединились к команде Angular в Google на заре Angular 1, Angular продемонстрировал феноменальный..

Путь AWS  — «Изучение машинного обучения — 10 начинающих ИИ и машинного обучения на AWS».
Универсальный ресурсный центр для изучения искусственного интеллекта и машинного обучения. НОЛЬ или ГЕРОЙ, начните свое путешествие здесь. Получите решения и пройдите обучение у экспертов AWS...

5 простых концепций Python, ставших сложными
#заранее извините 1) Переменные x = 4 y = 5 Переменная в Python — это символическое представление объекта. После присвоения некоторого объекта переменной Python мы приобретаем..

«Освоение вероятности: изучение совместной, предельной, условной вероятности и теоремы Байеса —…
Виды вероятности: Совместная вероятность Предельная вероятность Условная вероятность Диаграмма Венна в вероятностях: В “Set Theory” мы создаем диаграмму Венна...

Основы Spring: Bean-компоненты, контейнер и внедрение зависимостей
Как лего может помочь нашему пониманию Когда мы начинаем использовать Spring, нам бросают много терминов, и может быть трудно понять, что они все означают. Итак, мы разберем основы и будем..

Отслеживание состояния с течением времени с дифференцированием снимков
Время от времени что-то происходит и революционизирует часть моего рабочего процесса разработки. Что-то более забавное вместо типичного утомительного и утомительного процесса разработки. В..

Я предполагаю, что вы имеете в виду методы обработки категориальных данных.
Я предполагаю, что вы имеете в виду методы обработки категориальных данных. Пожалуйста, проверьте мой пост Инструментарий специалиста по данным для кодирования категориальных переменных в..