|
Программирование >> Разработка устойчивых систем
unsigned int currentEvenValue; Использование unsigned предотвращает public: потерю значимости. EvenGeneratorO { currentEvenValue = 0: } -EvenGeneratorO { cout -EvenGenerator endl: } int nextValueО { ++currentEvenValue: Опасный момент! ++currentEvenValue: return currentEvenValue: int mainO { EvenChecker::test<EvenGenerator>(): } /:- Существует опасность того, что проверяющий программный поток вызовет nextValue() после первого, но до второго инкремента currentEvenValue (в точке, помеченной комментарием Опасный момент! ). В результате будет получено неправильное (нечетное) значение. Чтобы доказать, что такое возможно, EvenChecker::test() создает группу объектов EvenChecker, которые постоянно читают выходные данные EvenGenerator и проверяют четность всех сгенерированных значений. Если среди них попадутся нечетные значения, программа выдает сообщение об ощибке и за-верщается. Вполне возможно, что проблема проявится лищь при очень большом количестве циклов EvenGenerator; все зависит от особенностей операционной системы и других деталей реализации. Чтобы это произошло гораздо скорее, попробуйте включить вызов yield() между первым и вторым инкрементом. Так или иначе, рано или поздно ошибка должна произойти, потому что программные потоки EvenChecker могут обращаться к данным EvenGenerator, находящимся в неправильном состоянии. Управление доступом в предыдущем примере продемонстрирована фундаментальная проблема, возникающая при использовании программных потоков: программист не знает, в какой момент выполняется тот или иной поток. Представьте, что вы сидите за столом и готовитесь нацепить на вилку последний кусок колбасы, лежащий на тарелке. Но когда вы уже потянулись за ним, колбаса вдруг исчезает (потому что ваш программный поток был приостановлен, а в это время явился другой едок и все съел). Такие проблемы приходится решать при написании многопоточных программ. Иногда вас совершенно не интересует, могут ли другие потоки обращаться к ресурсу во время его использования (например, при параллельном чтении данных). Но в большинстве случаев это существенно, и чтобы многопоточные приложения нормально работали, необходимы какие-то средства предотвращения одновременного доступа к ресурсу (хотя бы в критические моменты). Предотвращение подобных коллизий основано на простой блокировке ресурса на время его использования. Первый поток, обращающийся к ресурсу, блокирует его, после чего другие потоки уже не могут обратиться к ресурсу до снятия блокировки. Когда ресурс освобождается, он блокируется другими потоком и т. д. Итак, нам необходим механизм предотвращения обращений к памяти со стороны других задач в то время, пока эта память находится в неподходящем состоя- int mainO { EvenChecker::test<MutexEvenGenerator>(): } /:- Класс MutexEvenGenerator содержит мутекс (Mutex) с и.менем lock. Внутри функции nextValueO создается критическая секция, для чего используются функции acquire() и release(). Кроме того, между двумя инкрементами вставлен вызов yield(), чтобы повысить вероятность переключения контекста при нечетном значении currentEvenValue. Но поскольку мутекс ограничивает доступ к критической секции только одним потоком, сбоев не будет. Вызов yield() всего лищь ускоряет выявление ошибки, если бы она была теоретически возможна. От английского mutex. - Примеч. ред. НИИ. Другими словами, этот механизм должен исключать обращения к памяти в то время, пока она используется другой задачей. Концепция взаимного исключения занимает центральное место во всех многопоточных системах, а ее название обычно сокращается домутексаК В библиотеке ZThread объявления, относящиеся к механизму мутексов, находятся в заголовочном файле Mutex.h. Чтобы рещить проблему в представленной ранее программе, необходимо выделить критические секции, в которых должен действовать механизм взаимного исключения. Далее программный поток захватывает мутекс перед входом в критическую секцию и освобождает его в конце критической секции. В любой момент мутекс может быть захвачен только одним программным потоком, так что в результате достигается взаимное исключение: : Cll:MutexEvenGenerator.cpp Предотвращение коллизий с применением мутексов. {L} ZThread #1 nclude <iostream> #1nclude EvenChecker.h #1nclude zthread/ThreadedExecutor.h #include zthread/Mutex.h using namespace ZThread: using namespace std: class MutexEvenGenerator : public Generator { int currentEvenValue: Mutex lock: public: MutexEvenGenerator() { currentEvenValue = 0: } -MutexEvenGenerator0 { cout -MutexEvenGenerator endl: int nextValueО { lock.acquireO: ++currentEvenValue: Thread::yield(): ++currentEvenValue: int rval = currentEvenValue: lock.releasee): return rval: Обратите внимание: функция nextValueO должна сохранить возвращаемое значение внутри критической секции, потому что возврат управления из критической секции приведет к сохранению блокировки и невозможности ее повторного получения (обычно это приводит к ситуации взаимной блокировки, описанной в конце этой главы). Первый программный поток, входящий в nextValueO, устанавливает блокировку. Все остальные потоки, пытающиеся получить доступ в критическую секцию, приостанавливаются до момента снятия блокировки первым потоком. Когда это произойдет, планировщик выбирает другой поток, ожидающий освобождения ресурса. Таким образом, секция программы, защищенная мутексом, в любой момент доступна только для одного потока. Упрощенное программирование Если в программе организуется обработка исключений, программирование мутексов резко усложняется. Чтобы мутексы всегда освобождались, необходимо проследить за тем, чтобы каждый возможный путь обработки исключения В1слючал вызов release(). Кроме того, любая функция с несколькими путями возврата также должна позаботиться о вызове release() во всех соответствующих точках. Все эти проблемы легко решаются благодаря наличию у стековых (автоматических) объектов деструктора, который всегда вызывается независимо от того, как именно происходит выход из области видимости функции. В библиотеке ZThread эта возможность реализована в форме шаблона Guard. Шаблон Guard создает объекты, называемые стражами, которые при конструировании захватывают объект Lockable вызовом acquire(), а при уничтожении освобождают его вызовом release(). Объекты Guard, созданные в локальном стеке, уничтожаются автоматически независимо от способа выхода из функции и всегда снимают блокировку с объекта Lockable. Здесь приводится реализация предыдущего примера с применением стражей: : Cll:GuardedEvenGenerator.cpp Упрощенное программирование мутексов с применением шаблона Guard. {L} ZThread finclude <iostream> finclude EvenChecker.h finclude Zthread/ThreadedExecutor.h finclude zthread/Mutex.h finclude zthread/Guard.h using namespace ZThread: using namespace std: class GuardedEvenGenerator : public Generator { int currentEvenValue: Mutex lock: public: GuardedEvenGenerator0 { currentEvenValue = 0: } -GuardedEvenGeneratorO { cout -GuardedEvenGenerator endl: int nextValueO { Guard<Mutex> g(lock): ++currentEvenValue:
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |