Стандарты программирования на С++. 101 правило и рекомендация | страница 26



Заметим, что сказанное выше применимо независимо от того, является ли тип некоторым строковым типом, контейнером STL наподобие >vector или некоторым иным типом. (Мы заметили, что ряд авторов дают советы, из которых вытекает, что стандартные контейнеры представляют собой нечто отдельное. Это не так — контейнер представляет собой просто объект другого типа.) В частности, если вы хотите использовать компоненты стандартной библиотеки (например, >string или контейнеры) в многопоточной программе, проконсультируйтесь сначала с документацией разработчика используемой вами стандартной библиотеки, чтобы узнать, как именно следует пользоваться ею в многопоточном приложении.

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

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

• Внутренняя блокировка. Каждый объект сериализует все обращения к себе, обычно путем соответствующего блокирования всех открытых функций-членов, так что пользователю не надо предпринимать никаких дополнительных действий по сериализации использования объекта. Например, очереди производитель/потребитель обычно используют внутреннюю блокировку, поскольку сам смысл их существования состоит в совместном использовании разными потоками, и их интерфейсы спроектированы с использованием блокировок соответствующего уровня для отдельных вызовов функций-членов (>Push, >Pop). В общем случае заметим, что этот вариант применим, только если вы знаете две вещи.