Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 [ 100 ] 101 102 103 104 105

Приложение

Многопоточная библиотека в стиле минимализма

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

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

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

В приложении описываются средства и приемы, позволяющие разрабатывать машинно-независимые многопоточные объектно-ориентированные приложения на языке С++. Его нельзя рассматривать как полноценное введение в многопоточное программирование. Попытка рассмотреть все вопросы, связанные с разработкой многопоточной библиотеки, была бы бесполезной и обреченной на провал. Таким образом, мы сосредоточимся лишь на минимальных абстракциях, позволяющих создавать многопоточные компоненты.

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



П.1. Критика многопоточности

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

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

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

Основной альтернативой многопоточности является асинхронное выполнение (asinchronous execution). Режим асинхронного выполнения программы основан на модели обратных вызовов (callback model): при выполнении операции производится регистрация функции, которую следует вызвать после ее завершения. Основным недостатком асинхронного выполнения профаммы по сравнению с многопоточным режимом является избыток состояний профаммы. Используя асинхронную модель, нельзя проследить выполнение алгоритма шаг за шагом - можно лишь хранить состояние и позволять обратным вызовам его изменять. Поддержка такого состояния весьма проблематична и легко реализуется лишь для простейших операций.

Истинная многопоточная модель лишена таких недостатков. Каждый поток имеет неявное состояние, определяемое его точкой выполнения (оператором, который выполняется потоком в данный момент). Работу потока легко проследить, как если бы он был простой функцией. В асинхронной модели профаммист должен сам управлять точкой выполнения. (Основной вопрос асинхронного программирования: "Где я?".) В заключение отметим, что многопоточные профаммы могут реализовывать синхронную модель выполнения, что уже хорошо.

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

В однопоточном программировании безопасность данных при входе и выходе функции обычно гарантируется. Например, оператор присваивания (operateг=) из класса string считает обьект класса string корректным только перед началом и по-



еле завершения операции. В многопоточном программировании необходимо обеспечивать корректность объекта класса String даже во время выполнения оператора присваивания, поскольку другой поток может прервать выполнение этого оператора и применить к этому объекту другую операцию. В то время как однопоточное программирование приучает профаммистов рассмафивать функции как атомарные операции, в многопоточном профаммировании необходимо самому указывать, какие операции являются атомарными. Итак, многопоточные профаммы порождают большие проблемы при совместном использовании данных, что следует считать их недостатком.

Большинство способов многопоточного профаммирования основное внимание уделяют объектам синхронизации (synchronization objects), позволяющим обеспечивать последовательный доступ к совместно используемым ресурсам. При выполнении атомарной опера-хщи объект синхронизации захватывается. Если другие потоки пытаются захватить тот же объект синхронизации, они фиксируются. Затем данные изменяются, и объект синхронизации освобождается. В этот момент его может захватить другой поток, получив доступ к данным. Таким образом, потоки всегда работают с корректными данными.

В следующем разделе будут описаны разные способы захвата. Их перечень не исчерпывается объектами синхронизации, хотя эти объекты позволяют решить большинство задач многопоточного профаммирования.

П.2. Подход, реализованный в библиотеке Loki

Для решения проблем, связанных с многопоточностью, в библиотеке Loki определена Сфатегия ThreadingModel. Эта сфатегия представляет собой шаблонный класс с одним аргументом. Такой аргумент является типом языка С++, для которого реализуются функциональные возможности многопоточного профаммирования.

template <typename т> class SomeThreadingModel {

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

П.З. Атомарные операции с целочисленными типами

Допустим, что переменная х имеет тип i nt. Рассмофим оператор ++х;

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

Чтобы увеличить значение переменной х на единицу, процессор выполняет фи операции.

1. Извлекает переменную из памяти.

2. Увеличивает значение переменной в арифметико-логическом устройстве (АЛУ) процессора. Это единственное место, где выполняются операции - в памяти данных они лишь хранятся.

3. Переменная записывается обратно в память.



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 [ 100 ] 101 102 103 104 105