Программирование >>  Разработка устойчивых систем 

1 ... 174 175 176 [ 177 ] 178 179 180 ... 196


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:



1 ... 174 175 176 [ 177 ] 178 179 180 ... 196

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика