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

1 ... 177 178 179 [ 180 ] 181 182 183 ... 196


whileC !count->isPausecl()) { ++number: {

ostringstream os:

OS *this Total:

count->increment() endl: display->output(os):

Thread::sleep(100):

waitingForCancel - true: while(!count->isCanceled()) Задержка...

Thread::sleep(100): ostringstream os:

OS Terminating *this endl: display->output(os):

int getValueO { while(count->isPaused() && IwaitingForCancel)

Thread::sieep(100): return number:

friend ostream&

operator (ostream& os. const Entrances e) { return OS Entrance e.id : e.number:

int mainO {

srand(time(0)): Раскрутка генератора случайных чисел cout Press <ENTER> to quit endl: CountedPtr<Count> count(new Count): vector<Entrance*> v:

CountedPtr<Display> displayCnew Display): const int SZ - 5: try {

ThreadedExecutor executor: forCint i = 0: i < SZ: i++) {

Entrance* task = new EntranceCcount. display, i):

executor.executeCtask):

Сохранение указателя на задачу:

v.push back(task):

Cin.getO: Мдем. пока пользователь нажмет <Enter> count->pause(): Остановка подсчета int sum = 0:

vector<Entrance*>::iterator it = v.beginO: whileCit != v.endO) {

sum += C*it)->getValueC):

++it:

ostringstream os:

OS Total: count->valueC) endl Sum of Entrances: sum endl: display->outputCos):

count->cancelC): Завершение программных потоков } catchCSynchronization Exception& e) { cerr e.whatO endl:



В классе Count хранится суммарное количество посетителей парка. Единый объект Count, определенный в main() под именем count, хранится в классе Entrance под управлением CountedPtr, а, следовательно, совместно используется всеми объектами Entrance. В данном примере вместо обычного объекта Mutex задействован объект FastMutex с именем lock, потому что объект FastMutex опирается на поддержку мутексов операционной системы и дает более интересные результаты.

Объект Guard используется с lock в функции increment() для синхронизации доступа к count. Функция вызывает rand(), чтобы примерно в половине случаев между выборкой count в temp и сохранением temp в count включался вызов yield(). Если закомментировать определение объекта Guard, работа программы быстро нарушится, потому что сразу несколько потоков будут обращаться к переменной count и модифицировать ее одновременно.

Класс Entrance также содержит локальную переменную number с количеством посетителей, прошедших через данный вход. Это позволяет проверить объект count и убедиться в правильности количества зарегистрированных посетителей. Функция Entrance::run() просто увеличивает number с count и делает паузу продолжительностью в 100 миллисекунд.

В функции main() вектор vector<Entrance*> заполняется указателями на все созданные объекты Entrance. После нажатия пользователем клавиши Enter программа перебирает все объекты Entrance и суммирует их данные.

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

Все взаимодействие между объектами Entrance происходит в одном объекте Count. Когда пользователь нажимает клавишу Enter, функция main() отправляет count сообщение pause(). Так как все задачи Entrance::run() следят за приостановкой count, все объекты Entrance переходят в состояние waitingforCancel; в этом состоянии отсчет не производится, но объекты продолжают существовать. Это очень важно, поскольку функция main() должна обеспечить безопасный перебор объектов в векторе vector<Entrance*>. Обратите внимание: поскольку существует небольшая вероятность того, что перебор закончится раньше, чем Entrance завершит подсчет и перейдет в состояние waitingForCancel, функция getValue() в цикле вызывает функцию sleepO до тех пор, пока объект не перейдет в состояние waitingForCancel (это одна из форм так называемого активного ожидания, которое считается нежелательным; более предпочтительный подход с функцией wait() приведен далее). После того как main() завершит перебор вектора vector<Entrance*>, объекту count посылается сообщение cancel(), а все объекты Entrance снова ожидают изменения состояния. В этот момент они выводят сообщение о завершении и выходят из run(), что приводит к уничтожению всех задач механизмом многопоточности.

Запустите программу, и вы увидите, как по мере прохождения посетителей обновляются счетчики каждого входа, а также счетчик общего количества посетителей. Если закомментировать объект Guard в Count::increment(), вы заметите, что общее количество посетителей отличается от ожидаемого. Пока доступ к Counter синхронизируется при помощи мутекса, все работает нормально. Учтите, что Count::increment() нарочно увеличивает вероятность ошибки при помощи объекта temp и функции yield(). В реальных многопоточных программах вероят-



Это упрощенная формулировка. Иногда даже впенте безопасные атомарные операции оказываются небезопасными, поэтому вы должны очень .хорошо подумать, принимая рсчлемио об отка.чс от смп-хронизации. Отказ от сипхрони.зации часто является признаком поспеппюм оптимизации - того, что причиняет массу хлопот без сколько-1П1будь заметного выигрыша (а то и вовсе без выигрьнла). Атомарность операций - не единственный фактор. В многопроцессорных системах види.мость играет гораздо более важную роль, чем в однопроцессорных. Из.мснсмия, внссеп1н>1е одни.м потоком, даже атомарные в смысле непрерывности, могут остаться невидимыми для других потоков (например, храниться в локальном кэше процессора). Поэтому разные программные потоки будут по-разному представлять себе текущее состояние приложе1П1я. Механизм синхронизации обеспечивает распространение изменений, внесенных одним программным потоком, и их видимость во всем приложении, тогда как без синхронизации нельзя сказать, когда информация об изменениях дойдет до других потоков.

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

При возврате значения count функция Count::value() использует объект Guard для синхронизации. Это довольно любопытная подробность, хотя вероятно, что данный код будет работать в большинстве систем и компиляторов и без синхронизации. Дело в том, что в общем случае простые операции вроде возврата int являются атомарными; это означает, что они выполняются одной командой микропроцессора, которая не будет прерываться (механизм многопоточности не может прервать работу потока в середине выполнения команды микропроцессора). Иначе говоря, атомарные операции не прерываются механизмом многопоточности и не нуждаются в защите стражей. Более того, если бы мы переместили выборку count в temp, удалили yield() и просто увеличивали count напрямую, скорее всего, блокировка вообще не понадобилась бы, поскольку операция инкремента тоже обычно является атомарной.

К сожалению, стандарт С++ не гарантирует атомарности всех этих операций. Хотя такие операции, как возврат и инкремент int, почти наверняка являются атомарными на большинстве компьютеров, утверждать это с полной уверенностью нельзя. А раз гарантий нет, значит, нужно предполагать самое худшее. Иногда программисты проверяют атомарность операций на конкретном компьютере (обычно на основании анализа ассемблерного кода) и пишут код, основанный на этих предположениях. Такой подход всегда рискован, и применять его не рекомендуется. О полученных результатах слишком легко забыть. Следующий программист может ошибочно предположить, что программа легко переносится на другой компьютер, и сойдет с ума, пытаясь выявить нерегулярные ошибки, вызванные многопоточными коллизиями.

Итак, хотя удаление стража из Count::value() вроде бы не отражается на работе программы, такой подход небезопасен. На некоторых компьютерах он может вызвать аномалии в поведении программ.

Завершение при блокировке

Функция Entrance::run() в предыдущем примере включает вызов sleep() в основном цикле. Мы знаем, что функция sleep() со временем даст программе продолжить работу, и задача достигнет начала цикла, где она сможет закончить проверять



1 ... 177 178 179 [ 180 ] 181 182 183 ... 196

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