68897

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

Лекция

Информатика, кибернетика и программирование

Многозадачность (multitasking) – это способность операционной системы выполнять несколько программ одновременно. В основе реализации этого принципа на персональных ЭВМ лежит использование операционной системой аппаратного таймера для выделения отрезков времени (time sliced) для каждого из одновременно выполняемых процессов.

Русский

2014-09-26

61 KB

9 чел.

Лекция 10 Многозадачность и многопоточность

 Общие сведения

Многозадачность (multitasking) – это способность операционной системы выполнять несколько программ одновременно. В основе реализации этого принципа на персональных ЭВМ лежит использование операционной системой аппаратного таймера для выделения отрезков времени (time sliced) для каждого из одновременно выполняемых процессов. Если эти отрезки времени достаточно малы, и машина не перегружена слишком большим числом программ, то пользователю кажется, что все эти программы выполняются параллельно.

Многопоточность (multithreading)режим, при котором машинная программа содержит в себе параллельно исполняемые участки или процедуры (threads). Таким образом, механизм многопоточности дает возможность программе самой быть многозадачной.  

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

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

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

Вытесняющая многозадачность.

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

Схема обработки прерывания следующая:

  1.  Работа основной программы пользователя.
  2.  Возникновение прерывания.
  3.  Сохранение параметров работающей программы (регистров процессора).
  4.  Переход по адресу процедуры обработки прерывания.
  5.  Выполнение процедуры обработки прерывания.
  6.  Восстановление параметров работающей программы.
  7.  Переход по адресу следующей команды основной программы.

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

Прерывания использовались для написания так называемых резидентных (terminate-and-stay-resident, TSR) программ. Данные программы позволяли писать драйверы клавиатуры, спулеры печати, копировальщики экрана, которые работали в фоновом режиме. Но порядок работы процессора оставался прежним – в конкретный момент времени процессор выполнял только одну программу, и только эта программа могла вернуть управление той, которую прервала. И, если происходил сбой в работе резидентной программы или при обработке прерывания, компьютер "зависал" и не давал возможности вернутся к прерванной программе.

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

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

Невытесняющая многозадачность.

16-и разрядная Windows уже стала поддерживать и вытесняющую многозадачность (non-preemptive multitasking). Такой тип многозадачности стал возможен благодаря основанной на сообщениях архитектуре Windows. Windows – программа может находиться в памяти и не выполняться до тех пор, пока не получила сообщение. Ранее, эти сообщения часто являлись прямым или косвенным результатом ввода информации пользователем с клавиатуры или мыши. Сейчас, механизм посылки сообщений широко используется и для обмена данными, инициации какого-либо действия приложения и т.п.

При всем при этом, Windows использовала вытесняющую многозадачность для выполнения DOS – программ,  а также позволяла некоторым программам, например, для целей мультимедиа, получать аппаратные прерывания от таймера.

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

Частично преодолеть последнее ограничение, можно, опять таки используя таймер для периодической смене выполняемых программ. Другим решением по преодолению невытесняющей многозадачности является вызов функции PeekMessage. Обычно программа использует вызов функции GetMessage для извлечения сообщений из очереди. Однако, если в данный момент времени очередь сообщений пуста, то функция GetMessage будет ждать поступления сообщения в очередь, а затем возвратит его. Функция PeekMessage работает иначе – она возвращает управление программе даже в том случае, если сообщений в очереди нет. Таким образом, выполнение работы, требующей больших затрат времени, будет продолжаться до того момента, пока в очереди не появятся сообщения для данной или любой другой программы.

Многопоточность

В многопоточной среде программы могут быть разделены на части, называемыми потоками выполнения (threads), которые выполняются одновременно. В терминах программы "поток" – это прости функция, которая может также вызывать другие функции программы. Программа начинает выполнятся со своего главного (первичного) потока, который в традиционных программах на языке С является функцией main, а в Windows-программах – WinMain. Будучи выполняемой, функция может создавать новые потоки обработки, выполняя системный вызов с указанием функции инициализации потока (initial threading function). Операционная система в вытесняющем режиме переключает управление между потоками подобно тому, как она это делает с процессами. Таким образом, многопоточность есть реализация принципа многозадачности внутри программы.

Программная реализация многозадачности

Когда пользователь запускает программу, Windows создает в памяти компьютера экземпляр программы, называемый процессом. Процесс не является точной копией *.ехе – файла, как это было, например в операционной системе ДОС. Процесс содержит в себе копию *.ехе – файла, а также некоторую другую информацию о функционировании данного приложения. В этой дополнительной информации хранятся, например, границы выделенной приложению памяти, что помогает аппаратно отслеживать корректности обращения к оперативной памяти со стороны приложения. Так как Windows поддерживает механизм виртуальной памяти, то среди этой информации находятся сведения о расположении сегментов программы. Здесь же содержится командная строка, формируемая при запуске программы.

Запустить процесс можно как при помощи командной строки системного меню Windows, при помощи программы Проводника, так и программно, из другого приложения.  Это можно выполнить при помощи функций API:

Для 16-битных приложений используется функция WinExec:

UINT WinExec(

 LPCSTR lpCmdLine,  // command line

 UINT   uCmdShow// window style

);

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

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

В среде Windows 32 следует использовать другой способ порождения процессов:

BOOL CreateProcess(

   LPCTSTR lpApplicationName, // pointer to name of executable module

   LPTSTR lpCommandLine,  // pointer to command line string

   LPSECURITY_ATTRIBUTES lpProcessAttributes, // pointer to process security attributes

   LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread security attributes

   BOOL bInheritHandles,  // handle inheritance flag

   DWORD dwCreationFlags,  // creation flags

   LPVOID lpEnvironment,  // pointer to new environment block

   LPCTSTR lpCurrentDirectory, // pointer to current directory name

   LPSTARTUPINFO lpStartupInfo, // pointer to STARTUPINFO

   LPPROCESS_INFORMATION lpProcessInformation  // pointer to PROCESS_INFORMATION  

  );

Первый параметр является указателем на имя запускаемого файла. Имя может содержать полный путь к файлу (диск:\каталог\…\файл). Если имя не содержит пути, то операционная система ищет файл в текущем каталоге, затем в системных каталогах и в каталогах, указанных в разделе PATH при загрузке системы.

Второй параметр указывает на командную строку.

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

bInheritHandles и dwCreationFlags содержит дополнительные флаги управления созданием и приоритетом процесса.

lpEnvironment содержит указатель на буфер памяти, в котором будет создаваться служебная информация по процессу. Если равен NULL, то операционная система сама отводит место в памяти под эту информацию.

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

lpStartupInfo определяет структуру STARTUPINFO  , которая описывает окно, создаваемое для запускаемого процесса (в ней содержится информация, похожая на ту, которая передается в процедуру CreateWindow).

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

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

  1.  Непосредственное использование системного таймера для указание процедуры, вызываемой периодически. Данный способ был рассмотрен в лекции "Использование Таймера". Это типичный представитель вытесняющей многозадачности. К достоинствам этого способа можно отнести то, что программа может устанавливать и изменять период вызова функции.
  2.  Использование системного таймера для организации посылки синхронных сообщений выбранному окну (порядок организации описан в лекции "Использование Таймера"). С помощью данного способа реализуется невытесняющая многозадачность. Достоинство – изменяемая периодичность посылки сообщений. Недостаток – природа синхронных сообщений не гарантирует четкое выполнение периода прихода сообщений от таймера.
  3.  Создание потоков. Данный способ подразумевает определение некоторой процедуры потока, которая запускается параллельно основному процессу приложения. Момент окончания выполнения потока контролирует сама поточная процедура.

   CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread1,&params,0,&iThread);

Первый параметр определяет атрибуты безопасности для запускаемого потока. Если он равен NULL, то данный поток не может использоваться дочерними процессами.

Второй параметр определяет начальный адрес потока (фактически - имя процедуры потока), например:

DWORD WINAPI Thread1(PVOID pvoid)

{

// Текст потока

}

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

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

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

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

Итак, поток – это часть программы, запускаемая параллельно другим задачам процесса. Поток сам определяет как долго ему находится в памяти и какие действия надо совершать. Если поток должен периодически совершать одни и те же действия (например, обновлять экран или проверять почту), то самым естественным способом сделать это является организация бесконечного цикла (подобного циклу обработки сообщений). Однако, в этом случае не определено время цикличности, т.к. временные периоды выполнения потока определены операционной системой. Кроме того, как только одна итерация цикла заканчивается, начинается другая. Как же вызвать приостановку выполнения потока? Для этих целей существует функция Sleep, которая в качестве единственного параметра имеет время, задаваемое в миллисекундах. Функция не осуществляет возврата до тех пор, пока не истечет указанное время. При этом другие потоки и процессы работают в обычных режимах. Если параметр этой функции равен нулю, то операционная система просто лишает текущий поток оставшегося кванта времени.

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

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

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

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

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

Данный участок потока называется критическим разделом. Существует четыре функции работы с критическим разделом. Чтобы их использовать необходимо определеить объект типа критический раздел:

CRITICAL_SECTION cs;

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

InitializeCriticalSection(&cs);

Эта функция создает критический раздел с именем cs.После инициализации объекта "критический раздел" поток входит в критический раздел, вызывая функцию:

EnterCriticalSection(&cs);

В этот момент поток становиться владельцем объекта. Два различных потока не могут быть владельцами одного объекта одновременно. Следовательно, если один поток вошел в критический раздел, то следующий поток, вызывая функцию EnterCriticalSection(&cs); с тем же самым объектом, будет задержан внутри функции. Возврат из функции произойдет только тогда, когда первый поток покинет критический раздел, вызвав функцию:

LeaveCriticalSection(&cs);

В этот момент второй поток, задержанный в функции EnterCriticalSection(&cs), станет владельцем критического раздела, и его выполнение будет возобновлено.

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

DeleteCriticalSection(&cs);

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

Программа может создавать несколько критических разделов. Существенное ограничение при использовании критических разделов состоит в том, что таким образом можно синхронизировать только потоки одного процесса и нельзя осуществить синхронизацию процессов (действительно, ведь переменная cs объявлена в процессе и не видна из других процессов).

Использование событий

Альтернативным вариантом синхронизации является использование событий. Объект событие может быть либо свободным (signaled) или установленным (set), либо занятым (non-signaled) или сброшенным (reset). Вы можете создать объект "событие" с помощью функции:

hEvent = CreateEvent(&sa, fManual, fInitial, pszName);

Первый параметр - указатель на структуру SECURITY_ATTRIBUTES, устанавливающую параметры использования события потоками и процессами.

Параметр fInitial устанавливается в TRUE, если вы хотите, чтобы объект Событие был изначально свободным, или в FALSE, чтобы он был занятым.

Параметр pszName  определяет имя события. По этому имени разные процессы могут использовать одно событие.

Для того, чтобы сделать свободным существующий объект Событие, надо вызвать функцию:

SetEvent(hEvent);

Чтобы сделать объект Событие занятым, вызывается функция:

ResetEvent(hEvent);

 

Для синхронизации используется функция:

WaitForSingleObject(hEvent, dwTimeOut);

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

Если параметр fManual имеет значение FALSE при вызове функции CreateEvent, то объект Событие автоматически становится занятым, когда осуществляется возврат из функции WaitForSingleObject. Эта особенность позволяет избежать использования функции ResetEvent.

К вопросу о переменных

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


 

А также другие работы, которые могут Вас заинтересовать

16325. ИЗУЧЕНИЕ ВНЕШЕНЕГО ФОТОЭФФЕКТА 174.5 KB
  ИЗУЧЕНИЕ ВНЕШЕНЕГО ФОТОЭФФЕКТА Теоретическая часть Описание явления. Свет падающий на вещество передает этому веществу энергию в результате чего могут возникать разнообразные эффекты. Среди этих явлений важное место занимает внешний фотоэлектрический эффект ...
16326. ОПРЕДЕЛЕНИЕ ПОКАЗАТЕЛЯ ПРЕЛОМЛЕНИЯ СТЕКЛА ПРИ ПОМОЩИ МИКРОСКОПА 137.5 KB
  ОПРЕДЕЛЕНИЕ ПОКАЗАТЕЛЯ ПРЕЛОМЛЕНИЯ СТЕКЛА ПРИ ПОМОЩИ МИКРОСКОПА Теоретическая часть В основе определения показателя преломления стекла в данной работе используется один из фундаментальных законов геометрической оптики: закон преломления света. Согласно ...
16327. ИЗУЧЕНИЕ МИКРООБЪЕКТОВ ПРИ ПОМОЩИ МИКРОСКОПА 259.5 KB
  Лабораторная работа ИЗУЧЕНИЕ МИКРООБЪЕКТОВ ПРИ ПОМОЩИ МИКРОСКОПА Теоретические основы эксперимента Принцип действия микроскопа основан на формировании увеличенного изображения исследуемого объекта за счет увеличения угла зрения линзами. На рис.1 показан ход ...
16328. Поляризация света. Лабораторный практикум по общей физике 648.5 KB
  Поляризация света Лабораторный практикум по общей физике Оптика Содержание Часть I Теоретические основы эксперимента Электромагнитная природа света. Уравнения Максвелла Поперечность световой волны и поляризация света Поляризация при отражении
16329. Программирование алгоритмов линейной структуры 131.5 KB
  Лабораторная работа № 1 Программирование алгоритмов линейной структуры Цель: приобретение навыков программирования алгоритмов линейной структуры с помощью подпрограммыфункции вычисляющей значение арифметических выражений. Индивидуальные варианты лаборатор
16330. Программирование алгоритмов разветвляющейся структуры 293 KB
  Лабораторная работа № 2 Программирование алгоритмов разветвляющейся структуры Цель: приобретение навыков программирования алгоритмов разветвляющейся структуры с помощью пользовательской подпрограммыпроцедуры где на определенном этапе производится выбор очеред...
16331. Программирование алгоритмов ветвлений со многими вариантами 54.5 KB
  Лабораторная работа № 3 Программирование алгоритмов ветвлений со многими вариантами Цель: приобретение навыков программирования алгоритмов ветвлений со многими вариантами с помощью пользовательской подпрограммыфункции позволяющей выбрать необходимый вариант из...
16332. Программирование алгоритмов циклической структуры 128.5 KB
  Лабораторная работа № 4 Программирование алгоритмов циклической структуры Цель: приобретение навыков программирования алгоритмов циклической структуры с помощью подпрограммыпроцедуры позволяющую вычислять сумму произведение конечного ряда с помощью операторо
16333. Табулирование функции 209.5 KB
  Лабораторная работа № 5 Табулирование функции Цель: приобретение навыков программирования вычисления значений функции вида y=fx на промежутке [ab] с шагом h и z=fxy на промежутке [ab] и [cd] с шагом hx и hy с помощью пользовательской подпрограммыпроцедуры. Индивидуальные в