МЕНЮ


Фестивали и конкурсы
Семинары
Издания
О МОДНТ
Приглашения
Поздравляем

НАУЧНЫЕ РАБОТЫ


  • Инновационный менеджмент
  • Инвестиции
  • ИГП
  • Земельное право
  • Журналистика
  • Жилищное право
  • Радиоэлектроника
  • Психология
  • Программирование и комп-ры
  • Предпринимательство
  • Право
  • Политология
  • Полиграфия
  • Педагогика
  • Оккультизм и уфология
  • Начертательная геометрия
  • Бухучет управленчучет
  • Биология
  • Бизнес-план
  • Безопасность жизнедеятельности
  • Банковское дело
  • АХД экпред финансы предприятий
  • Аудит
  • Ветеринария
  • Валютные отношения
  • Бухгалтерский учет и аудит
  • Ботаника и сельское хозяйство
  • Биржевое дело
  • Банковское дело
  • Астрономия
  • Архитектура
  • Арбитражный процесс
  • Безопасность жизнедеятельности
  • Административное право
  • Авиация и космонавтика
  • Кулинария
  • Наука и техника
  • Криминология
  • Криминалистика
  • Косметология
  • Коммуникации и связь
  • Кибернетика
  • Исторические личности
  • Информатика
  • Инвестиции
  • по Зоология
  • Журналистика
  • Карта сайта
  • Теория многозадачности и многопоточности

    поэтому они разделяют все ресурсы процесса, такие как память и открытые

    файлы. Поскольку потоки разделяют память, отведенную программе, то они

    разделяют и статические переменные. Однако, у каждого потока есть свой

    собственный стек, и значит, автоматические переменные являются уникальными

    для каждого потока. Каждый поток, также, имеет свое состояние процессора,

    которое сохраняется и восстанавливается при переключении между потоками.

    Коллизии, возникающие при использовании потоков

    Собственно разработка, программирование и отладка сложного

    многопоточного приложения являются, естественно, самыми сложными задачами,

    с которыми приходится сталкиваться программисту для Windows. Поскольку в

    системе с вытесняющей многозадачностью поток может быть прерван в любой

    момент для переключения на другой поток, то может случайно произойти любое

    нежелательное взаимодействие между двумя потоками.

    Одной из основных ошибок в многопоточных программах является так

    называемое состояние гонки (race condition). Это случается, если

    программист считает, что один поток закончит выполнение своих действий,

    например, подготовку каких-либо данных, до того, как эти данные потребуются

    другому потоку. Для координации действий потоков операционным системам

    необходимы различные формы синхронизации. Одной из таких форм является

    семафор (semaphore), который позволяет программисту приостановить

    выполнение потока в конкретной точке программы до тех пор, пока он не

    получит от другого потока сигнал о том, что он может возобновить работу.

    Похожи на семафоры критические разделы (critical sections), которые

    представляют собой разделы кода, во время выполнения которого, поток не

    может быть прерван.

    Но использование семафоров может привести к другой распространенной

    ошибке, связанной с потоками, которая называется тупиком (deadlock). Это

    случается, когда два потока блокируют выполнение друг друга, а для того,

    чтобы их разблокировать необходимо продолжить работу.

    К счастью, 32-разрядные программы более устойчивы к определенным

    проблемам, включая проблемы с потоками, чем 16-разрядные программы.

    Например, предположим, что один поток выполняет простое действие:

    lCount++ ;

    где ICount – 32-разрядная глобальная переменная типа длинное целое,

    используемая другими потоками. В 16-разрядной программе, в которой такой

    оператор языка С транслируется в две инструкции машинного кода (сначала

    инкрементируется младшие 16 разрядов, а затем добавляется перенос в старшие

    16 разрядов). Допустим, что операционная система прервала поток между этими

    двумя инструкциями машинного кода. Если переменная ICount имела значение

    $0000FFFF, то после выполнения первой инструкции машинного кода ICount

    будет иметь нулевое значение. Если в этот момент произойдет прерывание

    потока, то другой поток получит нулевое значение переменной ICount. Только

    после окончания этого потока значение ICount будет увеличено на единицу до

    своего истинного значения $00010000.

    Такого рода ошибка может быть никогда не выявлена, поскольку довольно

    редко приводит к проблемам во время выполнения. Для 16-разрядных программ

    наилучший путь предотвратить такую ошибку – это поместить данное выражение

    в критический раздел, в рамках которого поток не может быть прерван. В 32-

    разрядной программе, однако, приведенное выражение является абсолютно

    корректным, поскольку оно компилируется в одну инструкцию машинного кода.

    Преимущества Windows

    Операционные системы Windows 95 и Windows NT не имеют последовательной

    очереди сообщений. Такое решение кажется очень хорошим: если программа

    выполняет длительную обработку сообщения, то курсор мыши принимает форму

    песочных часов при расположении над окном этой программы, и изменяется на

    обычную стрелку, если он располагается над окном другой программы. Простым

    щелчком кнопкой мыши можно перевести другое окно на передний план.

    Однако, пользователь по-прежнему не может работать с приложением,

    выполняющим длительную операцию, поскольку выполнение длительной операции

    предотвращает получение сообщений программой. А это нежелательно. Программа

    должна быть всегда открыта для сообщений, а это требует использования

    вторичных потоков.

    В Windows 95 и Windows NT не существует различия между потоками,

    имеющими очередь сообщений, и потоками без очереди сообщений. При создании

    каждый поток получает свою собственную очередь сообщений. Это снижает число

    ограничений, существующих для потоков в РМ-программе. (Однако, в

    большинстве случаев все еще обработка ввода и сообщений осуществляется в

    одном потоке, а протяженные во времени задачи передаются другим потокам,

    которые не создают окон.) Такая схема организации приложения, как мы

    увидим, почти всегда является наиболее разумной.

    В Windows 95 и Windows NT есть функция, которая позволяет одному

    потоку уничтожить другой поток, принадлежащий тому же процессу. Как вы

    обнаружите, когда начнете писать многопоточные приложения под Windows,

    иногда это очень удобно. Ранние версии операционной системы OS/2 не

    содержали функции для уничтожения потоков.

    Windows 95 и Windows NT поддерживают так называемую локальную память

    потока (thread local storage, TLS). Для того чтобы понять, что это такое,

    вспомним о том, что статические переменные, как глобальные так и локальные

    по отношению к функциям, разделяются между потоками, поскольку они

    расположены в зоне памяти данных процесса. Автоматические переменные

    (которые являются всегда локальными по отношению к функции) – уникальны для

    каждого потока, т. к. они располагаются в стеке, а каждый поток имеет свой

    стек.

    Иногда бывает удобно использовать для двух и более потоков одну и ту

    же функцию, а статические данные использовать уникальные для каждого

    потока. Это и есть пример использования локальной памяти потока. Существует

    несколько вызовов функций Windows для работы с локальной памятью потока.

    Фирма Microsoft ввела расширение в компилятор С, которое позволяет

    использовать локальную память потока более прозрачным для программиста

    образом.

    Новая усовершенствованная многопоточная программа

    Иногда имеет место тенденция использовать в программе каждую

    возможность, предлагаемую операционной системой. Нет смысла использовать

    множество потоков в программе, которая в этом не нуждается. Если программа

    выводит на экран курсор в виде песочных часов на достаточно долгий период

    времени, или, если она использует функцию PeekMessage для того, чтобы

    избежать появления курсора в виде песочных часов, то тогда идея

    реструктуризации программы в многопоточную, вероятно, может оказаться

    хорошей. В противном случае, вы только усложните себе работу и, возможно,

    внесете в программу новые ошибки.

    Есть даже некоторые ситуации, когда появление курсора мыши в виде

    песочных часов, может быть совершенно подходящим. Выше уже упоминалось

    "правило 1/10 секунды". Так вот, загрузка большого файла в память может

    потребовать больше времени, чем 1/10 секунды. Значит ли это, что функции

    загрузки файла должны были быть реализованы с использованием разделения на

    потоки? Совсем необязательно. Когда пользователь дает программе команду

    открыть файл, то он или она обычно хочет, чтобы операционная система

    выполнила ее немедленно. Выделение процесса загрузки файла в отдельный

    поток просто приведет к усложнению программы. Не стоит это делать даже ради

    того, чтобы похвастаться перед друзьями, что вы пишите многопоточные

    приложения.

    О использовании функции Sleep

    Выше было показано, как лучше организовать архитектуру программы,

    использующей многопоточность, а именно, чтобы первичный поток создавал все

    окна в программе, содержал все оконные процедуры этих окон и обрабатывал

    все сообщения. Вторичные потоки выполняют фоновые задачи или задачи,

    протяженные во времени.

    Однако, предположим, что требуется реализовать анимацию во вторичном

    потоке. Обычно анимация в Windows осуществляется с использованием сообщения

    WM_TIMER. Но если вторичный поток не создает окно, то он не может получить

    это сообщение. А без задания определенного темпа анимация могла бы

    осуществляться слишком быстро.

    Решение состоит в использовании функции Sleep. Поток вызывает функцию

    Sleep для того, чтобы добровольно отложить свое выполнение. Единственный

    параметр этой функции – время, задаваемое в миллисекундах. Функция Sleep не

    осуществляет возврата до тех пор, пока не истечет указанное время. В

    течение него выполнение потока приостанавливается и выделения для него

    процессорного времени не происходит (хотя очевидно, что для потока все-таки

    требуется какое-то незначительное время, за которое система должна

    определить, пора возобновлять выполнение потока или нет).

    Если параметр функции Sleep задан равным нулю, то поток будет лишен

    остатка выделенного ему кванта процессорного времени.

    Когда поток вызывает функцию Sleep, задержка на заданное время

    относится к этому потоку. Система продолжает выполнять другие потоки этого

    и других процессов

    Критический раздел

    В однозадачной операционной системе обычные программы не нуждаются в

    "светофорах" для координации их действий. Они выполняются так, как будто

    они являются хозяевами дороги, по которой они следуют. Не существует

    ничего, что могло бы вмешаться в то, что они делают.

    Даже в многозадачной операционной системе большинство программ

    выполняются независимо друг от друга. Но некоторые проблемы все же могут

    возникнуть. Например, двум программам может понадобиться читать и писать в

    один файл в одно и то же время. Для таких случаев операционная система

    поддерживает механизм разделения файлов (shared files) и блокирования

    отдельных фрагментов файла (record locking).

    Однако, в операционной системе, поддерживающей многопоточность, такое

    решение может внести путаницу и создать потенциальную опасность. Разделение

    данных между двумя и более потоками является общим случаем. Например,

    один поток может обновлять одну или более переменных, а другой может

    использовать эти переменные. Иногда в этой ситуации может возникнуть

    проблема, а иногда – нет. (Помните, что операционная система может

    переключать управление потоками только между инструкциями машинного кода.

    Если простое целое число разделяется между двумя потоками, то изменение

    этой переменной обычно осуществляется одной инструкцией машинного кода, и

    потенциальные проблемы сводятся к минимуму.)

    Однако, предположим, что потоки разделяют несколько переменных или

    структуру данных. Часто эти сложные переменные или поля структур данных

    должны быть согласованными между собой. Операционная система может

    прерывать поток в середине процесса обновления этих переменных. В этом

    случае поток, который затем использует эти переменные, будет иметь дело с

    несогласованными данными.

    В результате бы возникла коллизия, и нетрудно представить себе, как

    такого рода ошибка может привести к краху программы. В этой ситуации нам

    необходимо нечто похожее на светофор, который мог бы синхронизировать и

    координировать работу потоков. Таким средством и является критический

    раздел. Критический раздел – это блок кода, при выполнении которого поток

    не может быть прерван.

    Имеется четыре функции для работы с критическими разделами. Чтобы их

    использовать, вам необходимо определить объект типа критический раздел,

    который является глобальной переменной типа CRITICAL_SECTION. Например,

    CRITICAL_SECTION CS ;

    Тип данных CRITICAL_SECTION является структурой, но ее поля

    используются только внутри Windows. Объект типа критический раздел сначала

    должен быть инициализирован одним из потоков программы с помощью функции:

    InitializeCriticalSection (&cs);

    Эта функция создает объект критический раздел с именем cs. В

    документации содержится следующее предупреждение: "Объект критический

    раздел не может быть перемещен или скопирован. Процесс также не должен

    модифицировать объект, а должен обращаться с ним, как с "черным ящиком"."

    После инициализации объекта критический раздел поток входит в

    критический раздел, вызывая функцию:

    EnterCriticalSection (&cs) ;

    В этот момент поток становится владельцем объекта. Два различных

    потока не могут быть владельцами одного объекта критический раздел

    одновременно. Следовательно, если один поток вошел в критический раздел, то

    следующий поток, вызывая функцию EnterCriticalSection с тем же самым

    объектом

    критический раздел, будет задержан внутри функции. Возврат из функции

    произойдет только тогда, когда первый поток покинет критический раздел,

    вызвав функцию:

    LeaveCriticalSection (&cs);

    В этот момент второй поток, задержанный в функции

    EnterCriticalSection, станет владельцем критического раздела, и его

    выполнение будет возобновлено.

    Когда объект критический раздел больше не нужен вашей программе, его

    можно удалить с помощью функции:

    DeleteCriticalSection (&cs);

    Это приведет к освобождению всех ресурсов системы, задействованных для

    поддержки объекта критический раздел.

    Механизм критических разделов основан на принципе взаимного исключения

    (mutual exclusion). Этот термин нам еще встретится при дальнейшем

    рассмотрении синхронизации потоков. Только один поток может быть владельцем

    критического раздела в каждый конкретный момент времени. Следовательно,

    один поток может войти в критический раздел, установить значения полей

    структуры и выйти из критического раздела. Другой поток, использующий эту

    структуру, также мог бы войти в критический раздел перед осуществлением

    доступа к полям структуры, а затем выйти из критического раздела.

    Обратите внимание, что возможно определение нескольких объектов типа

    критический раздел, например, cs1 и cs2. Если в программе имеется четыре

    потока, и два первых из них разделяют некоторые данные, то они могут

    использовать первый объект критический раздел, а два других потока, также

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

    раздел.

    Обратите внимание, что надо быть весьма осторожным при использовании

    критического раздела в главном потоке. Если вторичный поток проводит

    слишком много времени в его собственном критическом разделе, то это может

    привести к "зависанию" главного потока на слишком большой период времени.

    Объект Mutex

    Существует одно ограничение в использовании критических разделов. Оно

    заключается в том, что их можно применять для синхронизации потоков только

    в рамках одного процесса. Но бывают случаи, когда необходимо

    синхронизировать действия потоков различных процессов, которые разделяют

    какие-либо ресурсы (например, память). Использовать критические разделы в

    такой ситуации нельзя. Вместо них подключаются объекты типа mutex (mutex

    object).

    Составное слово "mutex" происходит из словосочетания "mutual

    exclusion", что означает взаимное исключение, и очень точно отражает

    назначение объектов. Мы хотим предотвратить возможность прерывания потока в

    программе до тех пор, пока не будет выполнено обновление или использование

    разделяемых данных.

    Уведомления о событиях

    Мы можем определить понятие большой работы как действия, выполняя

    которые, программа нарушит "правило 1/10 секунды". Примерами большой работы

    могут служить: проверка орфографии в текстовых процессорах, сортировка и

    индексирование файлов баз данных, пересчет электронной таблицы, печать и

    даже сложное рисование. Конечно, как мы уже знаем, лучшее решение состоит в

    следовании "правилу 1/10 секунды", т. е. в передаче большой работы

    вторичным потокам обработки. Эти вторичные потоки не создают окон и,

    значит, не ограничены "правилом 1/10 секунды".

    Часто бывает, что вторичному потоку надо проинформировать первичный

    поток о том, что он завершился, или первичному потоку надо прервать работу,

    выполняемую вторичным потоком.

    Локальная память потока

    Глобальные переменные в многопоточных программах (так же как и любая

    выделенная память) разделяются между всеми потоками в программе. Локальные

    статические переменные функций также разделяются между всеми потоками,

    использующими эту функцию. Локальные автоматические переменные в функции

    являются уникальными для каждого потока, потому что они хранятся в стеке, а

    каждый поток имеет свой собственный стек.

    Может возникнуть необходимость иметь постоянную область памяти,

    уникальную для каждого потока. Например, функция strtok языка С, которая

    уже упоминалась в этой главе, требует такого типа память. Нет сомнений, что

    С его не поддерживает. В Windows 95 имеется четыре функции, поддерживающие

    эту память, которая называется локальной памятью потока (thread local

    storage, TLS).

    Первичный поток вызывает функцию JTsAlloc для получения значения

    индекса:

    dwTlsIndex = TIsAlloc () ;

    Он может храниться в глобальной переменной или может быть передан

    функции потока в параметре-структуре.

    Функция потока начинается с выделения памяти для структуры данных и с

    вызова функции TIsSetValue, используя индекс, полученный ранее:

    TIsSetValue (dwTlsIndex, GlobalAlloc (GPTR, sizeof (DATA))) ;

    Это действие устанавливает соответствие указателя с конкретным потоком

    и конкретным индексом в потоке. Теперь, любая функция, которой нужно

    использовать этот указатель (включая саму базовую функцию потока), может

    использовать код, подобный такому:

    PDATA pdata ;

    pdata = (PDATA) TIsGetValue (dwTlsIndex) ;

    Теперь она может изменять значения pdata->a и pdata->b. Перед

    завершением функции потока необходимо освободить захваченную память:

    GlobalFree (TIsGetValue (dwTlsIndex)) ;

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

    поток освобождает индекс:

    TIsFree (dwTlsIndex) ;

    Полезно посмотреть как организована локальная память потока. (Мне

    неизвестно, как в действительности Windows 95 это делает, но описываемая

    схема вполне правдоподобна.) Во-первых, функция TIsAlloc могла бы просто

    выделить блок памяти (длиной 0 байт) и вернуть значение индекса, который

    является указателем на этот блок. Каждый раз при вызове функции TIsSet

    Value с этим индексом блок памяти увеличивается на 8 байт с помощью функции

    GlobalReAlloc. В этих 8 байтах хранятся идентификатор потока, вызывающего

    функцию, полученный с помощью функции GetCurrentThreadID, и указатель,

    переданный функции TIsSetValue. Функция TIsGetValue просто использует

    идентификатор потока для поиска в таблице, а затем возвращает указатель.

    Функция TZsFree освобождает блок памяти.

    Реализация многопоточности в Delphi

    Стандартный мастер модулей в Delphi автоматически создает модуль

    содержащий класс потока с указанным именем. Весь код который необходимо

    вынести в отдельный поток помещается в метод класса Execute.

    Базовый класс для создания потока пользователя – TThread

    TThread = class

    protected

    procedure DoTerminate; virtual;

    procedure Execute; virtual;

    procedure Synchronize(Method: TThreadMethod);

    property ReturnValue: Integer;

    property Terminated: Boolean;

    public

    constructor Create(CreateSuspended: Boolean);

    procedure Resume;

    procedure Suspend;

    procedure Terminate;

    function WaitFor: LongWord;

    property FreeOnTerminate: Boolean;

    property Handle: Thandle;

    property Priority: TthreadPriority;

    property Suspended: Boolean;

    property ThreadID: Thandle;

    property OnTerminate: TnotifyEvent;

    end;

    Процесс, породивший поток может гибко управлять его состоянием:

    приоритетом Priority; приостановить и продолжить его исполнения, а так же

    досрочно завершить выполнение потока.

    Для вызова методов VCL необходимо синхронизировать дочерний поток с

    главным. Для этого служит процедура Synchronize(Method:TThreadMethod);

    unit Unit1;

    interface

    uses

    Classes;

    type

    TSamples = class(TThread)

    private

    { Private declarations }

    protected

    procedure Execute; override;

    end;

    implementation

    { Подсказка Delphi по поводу Synchronize.

    Important: Methods and properties of objects in VCL can only be used in a

    method called using Synchronize, for example,

    Synchronize(UpdateCaption);

    and UpdateCaption could look like,

    procedure Samples.UpdateCaption;

    begin

    Form1.Caption := 'Updated in a thread';

    end; }

    { Samples }

    procedure TSamples.Execute;

    begin

    { Здесь должен быть размещен код потока }

    end;

    end.

    Список используемой литературы

    1. Turbo Pascal for Windows в 2-х томах. Нейл Рубенкинг Пер. с англ. –

    М.:Мир, 1993, 536 с., ил.

    2. Теория и практика C++. Герберт Шилдт. пер. с англ. – СПб.: BHV – Санкт-

    Петербург, 1996. 416 с., ил.

    3. Программирование для Windows 95; в 2-х томах. Чарльз Петзолд. пер. с

    англ. – СПб.: BHV – Санкт-Петербург, 1997.– 752 с., ил.

    4. Микропроцессоры 80x86 Архитектура, функционирование. В.М.Михальчук

    А.А.Ровдо С.В.Рыжиков Мн.: Битрикс, 1994. - 400с.

    В моей работе были рассмотрены основные принципы многозадачности и

    многопоточности. Приведены примеры оформления многопоточных приложений на

    Delphi. В дальнейшем многозадачная технология получит дополнительное

    развитие. В последнем вышедшем релизе Windows под названием Windows2000 для

    поддержки многозадачности используются так называемые объекты-задания

    симметрично распределяющиеся между процессорами. Для ускорения работы

    мощных серверных систем используют машины на базе 2,4 и даже 8 процессорах.

    Такое серверное программное обеспечение как Windows NT4.0, Unix, Linux так

    же поддерживает SMP – симметричную мультипроцессорную обработку данных.

    Работа таких систем была бы невозможна без использования алгоритмов

    многозадачности и многопоточности.

    Страницы: 1, 2


    Приглашения

    09.12.2013 - 16.12.2013

    Международный конкурс хореографического искусства в рамках Международного фестиваля искусств «РОЖДЕСТВЕНСКАЯ АНДОРРА»

    09.12.2013 - 16.12.2013

    Международный конкурс хорового искусства в АНДОРРЕ «РОЖДЕСТВЕНСКАЯ АНДОРРА»




    Copyright © 2012 г.
    При использовании материалов - ссылка на сайт обязательна.