|
Программирование >> Разработка устойчивых систем
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() со временем даст программе продолжить работу, и задача достигнет начала цикла, где она сможет закончить проверять
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |