5766

Процессы и потоки

Реферат

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

Процессы и потоки Основные понятия Процесс (process) - это отдельная исполняемая программа с используемой ею памятью и другими выделяемыми ей ресурсами. Многозадачность (multitasking) - это способность операционной системы выполнять несколько п...

Русский

2012-12-19

188.5 KB

8 чел.

Процессы и потоки

Основные понятия

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

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

32-разрядные версии Windows кроме многозадачности поддерживают еще и многопоточность (multithreading). Многопото́чность — способ выполнения процесса, при котором вычисления разбиваются на несколько потоков, выполняющихся «параллельно», т.е. без предписанного порядка во времени. При выполнении некоторых задач такое разделение может достичь более эффективного использования ресурсов вычислительной машины.

Все потоки выполняются в адресном пространстве процесса

Поток (thread) – это последовательность исполняемых команд. Процесс может состоять из единственного потока, а может содержать их несколько

Итак,

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

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

К достоинствам многопоточности в программировании можно отнести следующее:

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

Повышение производительности процесса за счет распараллеливания процессорных вычислений и операций ввода/вывода.

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

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

Основным фактором, учитываемым при диспетчеризации, служит приоритет выполнения (execution priority) каждого потока. В Win32 каждому потоку присваивается свой приоритет - от 0 до 31; чем больше число, тем выше приоритет. Приоритет 31 резервируется под особенно критичные операции, например, прием данных в реальном режиме времени. Приоритет 0 назначается операционной системой самым второстепенным задачам, выполнение которых происходит в то время, когда нет других задач. Большинство потоков работают с приоритетом от 7 до 11.

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

Проблемы многопоточной архитектуры

Приведем рекомендации по архитектуре многопоточных программ.

Рекомендуется, чтобы первичный или главный (primary) поток программы создавал все окна и соответствующие им оконные процедуры, необходимые в программе, и обрабатывал все сообщения для этих окон.

Все остальные потоки должны быть вторичными (secondary), выполняющими фоновые задачи и не имеющими интерактивной связи с пользователем, кроме как через первичный поток.

Рассмотрим проблему разделения ресурсов.

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

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

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

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

Итак,

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

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

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

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

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

Одной из основных ошибок в многопоточных программах является так называемое состояние гонки (race condition). Это случается, если программист считает, что один поток закончит выполнение своих действий, например, подготовку каких-либо данных, до того, как эти данные потребуются другому потоку.

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

  •  Взаимоисключения (mutex, мьютекс) — это объект синхронизации, который устанавливается в особое сигнальное состояние, когда не занят каким-либо потоком. Только один поток владеет этим объектом в любой момент времени, отсюда и название таких объектов (от английского mute - молчаливый, приглушать звук) — одновременный доступ к общему ресурсу исключается. После всех необходимых действий мьютекс освобождается, предоставляя другим потокам доступ к общему ресурсу.
  •  Семафоры представляют собой доступные ресурсы, которые могут быть приобретены несколькими потоками в одно и то же время, пока пул ресурсов не опустеет. Тогда дополнительные потоки должны ждать, пока требуемое количество ресурсов не будет снова доступно. Семафоры очень эффективны, поскольку они позволяют одновременный доступ к ресурсам.
  •  Критические секции обеспечивают синхронизацию подобно мьютексам за исключением того, что объекты, представляющие критические секции, доступны в пределах одного процесса. События, мьютексы и семафоры также можно использовать в однопроцессном приложении, однако критические секции обеспечивают более быстрый и более эффективный механизм взаимно-исключающей синхронизации. Подобно мьютексам объект, представляющий критическую секцию, может использоваться только одним потоком в данный момент времени, что делает их крайне полезными при разграничении доступа к общим ресурсам.
  •  События. События полезны в тех случаях, когда необходимо послать сообщение потоку, сообщающее, что произошло определенное событие. Например, при асинхронных операциях ввода и вывода из одного устройства, система устанавливает событие в сигнальное состояние когда заканчивается какая-либо из этих операций. Один поток может использовать несколько различных событий в нескольких перекрывающихся операциях, а затем ожидать прихода сигнала от любого из них.
  •   Замечание. Использование семафоров может привести к другой распространенной ошибке, связанной с потоками, которая называется тупиком (deadlock). Это случается, когда два потока блокируют выполнение друг друга, а для того, чтобы их разблокировать, необходимо продолжить работу.

Функции создания потока и его завершения

Для создания потока можно использовать функцию из стандартной библиотеки языка С, определенную в файле process.h следующим образом и возвращающую дескриптор созданного потока:

 

 unsigned long _beginthread( void( __cdecl *start_address )( void * ),

   unsigned stack_size, void *arglist );

 

Через первый параметр start_address функции _beginthread передается адрес функции потока. После вызова функции _beginthread код функции потока, так же как и код любой другой функции, которая может быть вызвана из этой функции потока, выполняется одновременно с оставшимся кодом программы.

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

Второй параметр stack_size функции _beginthread служит для задания размера стека для потока. Если через этот параметр передать нулевое значение, то операционная система полагает размер стека для потока равным размеру стека главного потока.

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

Завершение работы потока происходит при вызове функций:

 

void _endthread( void );

 

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

Для создания потока можно использовать и функцию стандартной библиотеки языка С _beginthreadex. С ее помощью можно задавать более разнообразные параметры создаваемого потока. Закрывать же поток, созданный этой функцией, следует при помощи функции _endthreadex.

Кроме рассматриваемых выше функций библиотеки С для работы с потоками можно использовать и функции WinApi 32: CreateRemoteThread, CreateThread, ExitThread, GetExitCodeThread, GetThreadPriority, ResumeThread, SetThreadPriority.

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

На этапе компиляции переменную CFLAGS нужно заменить переменной CFLAGSMT. Это та же переменная, но с добавлением флага _MT. Это приводит, в частности, к тому, что компилятор вставляет имя файла libcmt.lib вместо libc.lib. Эта информация используется компоновщиком.

При создании многопоточного приложения в среде MS-Visual C++ необходимо при помощи пункта меню “Settings” внести изменения в опции проекта в диалоге “Project Settings”:

·         закладка “C/C++” - в поле “Preprocessor definition” добавить флаг _MT;

·         закладка “Link” - в поле “Object/library modules” добавить библиотеку libcmt.lib, затем включить переключатель “Ignore all defaults libraries”.)

Примерные действия по созданию потока

Каждый поток имеет свою функцию – функцию потока. Пока выполнение этой функции не завершено, поток продолжает оставаться выполняемым. Эта функция должна быть объявлена следующим образом (в качестве имени потоковой функции выберем ThreadFun):

 

void ThreadFun(void *lpvoid);

 

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

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

Пусть созданием и остановкой потока, а также типом выполняемого потоком действия (видом рисунка), управляет пользователь при помощи команд меню, имеющих идентификаторы:

 

 #define IDM_CIRCLES 1

#define IDM_BARS 2

#define IDM_STOP 3

#define IDM_BEGIN 4

 

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

 

struct PARAM

{ BOOL type;  // тип выполняемого потоком действия

 HWND wnd;  // окно, в оконной процедуре которого создается поток

 BOOL stop;  // признак завершения работы потока

};

 

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

 

 LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

 {

 static struct PARAM p; // используется как общая память потоков

 . . .

 switch(msg)

 {

  case WM_COMMAND: // обработка сообщений от меню

  { switch(LOWORD(wParam))

   {

    case IDM_BEGIN: // запуск потока на выполнение

    { // запись в общую память потоков,

      p.type=0; p.wnd=hWnd; p.stop=0;

     // создание и немедленный запуск потока

     _beginthread(ThreadFun,0,(void *)(&p));

    }; break;

 

    case IDM_STOP: // полная остановка потока

    { // записать признак завершения потока

      p.stop=1;

    }; break;

 

    case IDM_CIRCLES: // изменить тип действия

    { // записать тип выполняемого потоком действия

     p.type=0;

    }; break;

 

    case IDM_BARS: // изменить тип действия

    { // записать тип выполняемого потоком действия

     p.type=1;

    }; break;

   }

  };return 0l;

 

  case WM_DESTROY:

  { // записать признак завершения потока

    p.stop=1;

   PostQuitMessage(0);

  };  return 0l;

 

  // обработка других сообщений

  . . .

  default: return DefWindowProc(hWnd, msg, wParam, lParam);

 }

 return 0l;

}

 

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

 

 void ThreadFun(void *lpvoid)

{

 // автоматические переменные потока

 . . .

 

 // получение адреса общей памяти потоков

  struct PARAM *p=(struct PARAM *)lpvoid;

 

 while(!p->stop) // пока признак завершения не установлен

 {

  // выполнение действия, определяемого p->type, при этом может

  // использоваться дескриптор создавшего поток окна, он хранится в p->wnd

  . . .

  Sleep(1000); // задержка на 1 сек

 }

 

 _endthread(); // полная остановка потока

}

 

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

Приостановка выполнения потока

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

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

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

Функция Sleep не осуществляет возврата до тех пор, пока не истечет указанное время. В течение него выполнение потока приостанавливается и выделения для него процессорного времени не происходит (не считая того незначительного количества времени, необходимого для того, чтобы система могла проверить, пора возобновлять выполнение потока или нет).

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

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

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

Синхронизация потоков

Синхронизация потоков необходима тогда, когда действия потоков зависят друг от друга.

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

Даже в многозадачной операционной системе большинство программ выполняются независимо друг от друга. Но некоторые проблемы все же могут возникнуть. Например, двум программам может понадобиться читать и писать в один файл в одно и то же время. Для таких случаев операционная система поддерживает механизм разделения файлов (shared files) и блокирования отдельных фрагментов файла (record locking).

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

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

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

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

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

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

Отмеченные выше средства синхронизации можно разделить на критические секции и объекты ядра

Критическая секция - это блок кода, при выполнении которого поток не может быть прерван. 

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

Перед тем, как использовать функции для работы с критическими разделами, необходимо определить объект критический раздел, который является глобальной переменной типа CRITICAL_SECTION. Например:

 

CRITICAL_SECTION cs; // объявление глобальной переменной

 

Тип данных CRITICAL_SECTION является структурой, но ее поля используются только самой Windows.

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

 

InitializeCriticalSection(&cs);

 

Эта функция создает критический раздел с именем cs. Чаще всего этот объект создается при инициализации процесса, т.е. главным потоком в функции WinMain.

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

 

EnterCriticalSection(&cs);

 

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

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

 

LeaveCriticalSection(&cs);

 

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

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

 

EnterCriticalSection(&cs);

// критичекий фрагмент кода потока

...................................

LeaveCriticalSection(&cs);

 

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

 

DeleteCriticalSection(&cs);

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

Механизм критических разделов основан на принципе взаимного исключения (mutual exclusion). Только один поток может быть владельцем критического раздела в каждый конкретный момент времени. Следовательно, один поток может войти в критический раздел, установить значения полей структуры и выйти из критического раздела. Другой поток, использующий эту структуру, также мог бы войти в критический раздел с тем же именем перед осуществлением доступа к полям структуры, а затем выйти из критического раздела.

Следует отметить, что

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

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

·         Следует очень осторожно использовать использование критического раздела в главном потоке. Если вторичный поток проводит слишком много времени в его собственном критическом разделе, то это может привести к “зависанию” главного потока на слишком большой период времени.

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

Но

Бывают случаи, когда необходимо синхронизировать действия потоков различных процессов, которые разделяют какие-либо ресурсы (например, память). Использовать критические разделы в такой ситуации нельзя. Вместо них используются мьютексы (mutex object).

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

Уведомление при помощи посылки сообщений

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

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

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

Например, модифицируем функцию вторичного потока предыдущего примера так, чтобы она посылала сообщение в первичный поток через каждые 1000 итераций работы своего цикла, а функцию главного окна приложения - чтобы она обрабатывала это сообщение:

 

 . . .

#define MESSAGE_FROM_OTHER_THREAD (WM_USER+1)

. . .

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

{ ...

 switch(msg)

 { ...

  case MESSAGE_FROM_OTHER_THREAD:

  { // первичный поток может здесь обработать данное сообщение

   . . .

  };break;

 

  . . .

 }

 return 0l;

}

 

void ThreadFun(void *lpvoid)

{

 long num=0;

 . . .

 

 while(!p->stop)

 {

  num++;

  if (num%1000==0)

  { // посылка сообщения первичному потоку

   SendMessage(p->wnd,MESSAGE_FROM_OTHER_THREAD,0,0L);

  }

  . . .

 }

 _endthread();

}

Объекты ядра

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

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

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

Так, объекты ядра «процесс» сразу после создания всегда находятся в занятом состоянии. В момент завершения процесса операционная система автоматически освобождает его объект ядра "процесс", и он на всегда остается в этом состоянии

Объект ядра «процесс» пребывает в занятом состоянии, пока выполняется сопос тавленный с ним процесс, и переходит в свободное состояние, когда процесс завершается Внутри этого объекта поддерживается булева переменная, которая при создании объекта инициализируется как FALSE («занято"). По окончании работы процесса операционная система меняет значение этой переменной на TRUE, сообщая тем самым, что объект свободен.

Существуют специальные функции, которые позволяют потоку ждать перехода определенного объекта ядра в свободное состояние.

Wait-функции

Wait-функции позволяют потоку в любой момент приостановиться и ждать освобож дения какого-либо объекта ядра. Из всего семейства этих функций чаще всего исполь зуется WaitForSingleObject: 

DWORD WaitForSingleObject( HANDLE hObject, DWORD dwMilliseconds);

Первый параметр, hObject, идентифицирует объект ядра, поддерживающий состояния «свободен-занят» Второй параметр, dwMilliseconds, указывает, сколько времени (в миллисекундах) поток готов ждать освобождения объекта.

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

WaitForSingleObject(hProcess, INFINITE);

Константа INFINITE, подсказывает системе, что вызывающий поток готов ждать этого события хоть целую вечность. Именно эта константа обычно и передается функции WaitForSingleObject, но Вы можете указать любое значение в миллисекундах. Кстати, константа INFINITE опре делена как 0xFFFFFFFF (или -1). Разумеется, передача INFINlTE нс всегда безопасна Если объект так и не перейдет в свободное состояние, вызывающий поток никогда не проснется; одно утешение, тратить драгоценное процессорное время он при этом не будет

Вот пример, иллюстрирующий, как вызывать WaitForSingleObject co значением тай маута, отличным от INFINITE

DWORD dw = WaitForSlngleObject(hProcess, 5000);

switch (dw)

{
case WAIT_OBJECT_0:
//
процесс завершается
break;

case WAIT_TIMEOUT:
// процесс не завершился в течение 5000 мс
break;

case WAIT_FAILED:
// неправильный вызов функции (неверный описатель?)
break;
}

Данный код сообщает системе, что вызывающий поток не должен получать про цессорное время, пока не завершится указанный процесс или не пройдет 5000 мс (в зависимости от того, что случится раньше). Поэтому функция вернет управление либо до истечения 5000 мс, если процесс завершится, либо примерно через 5000 мс, если процесс к тому времени не закончит свою работу Заметьте, что в параметре dwMilli seconds можно передать 0, и гогда WaitForSingleObject немедленно вернет управление

Возвращаемое значение функции WaitForSingleObject указывает, почему вызывающий поток снова стал планируемым Если функция возвращает WAITOBTECT_0, объект свободен, а если WAIT_TIMEOUT — заданное время ожидания (таймаут) истекло. При передаче неверного параметра (например, недопустимого описателя) WaitForSing leObject возвращает WAIT_ EAILED. Чтобы выяснить конкретную причину ошибки, вы зовите функцию GetLastErroY. 

Функция WaitForMultipleObjects аналогична WaitForSingleObject c тем исключением, что позволяет ждать освобождения сразу нескольких объектов или какого-то одного из списка объектов:

DWORD WaitForMultipleObjects( DWOHD dwCount, CONST HANDLE* phObjects, BOOL fWaitAll, DWORD dwMilliseconds);

Параметр dwCount определяет количество интересующих объектов ядра Его значение должло быть в пределах от 1 до MAXIMUM_WAIT_OBJECTS (в заголовочных файлах Windows оно определено как 64). Параметр phObject — это указатель на массив описателей объектов ядра.

WaitForMultipleObjects приостанавливает поток и заставляет его ждать освобождения либо всех заданных объектов ядра, либо одного из них. Параметр fWaitAll - если он равен TRUE, функция не даст потоку возобновить свою работу, пока не освободятся все объекты.

Параметр dwMilliseconds идентичен одноименному параметру функции WaitFor SingleObject.

Возвращаемое значение функции WaitForMultipleObjects сообщает, почему возобновилосъ выполнение вызвавшего ее потока Значения WAIT_FAILED и WAIT_TIMEOUT никаких пояснений не требуют. Если Вы передали TRUE в параметре fWaitAll и всс объекты перешли в свободное состояние, функция возвращает значение WAIT_OBJECT_0. Если fWaitAll приравнен FALSE, она возвращает управление, как только освобождается любой из объектов. В этом случае возвращается значение от WAIT_OBJECT_0 до WAIT_OBJECT_0 + dwCount – 1 - это индекс в массиве описателей, на который указывает второй параметр функции WaitForMultipleObjects, какой объект перешел в незанятое состояние. Пример.

HANDLE h[3];
h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3,

DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);

switch (dw)
{

case WAIT_FAILED:
// неправильный вызов функции (неверный описатель?)
break;

case WAIT_TIMEOUT:
// ни один из объектов не освободился в течение 5000 мс
break;

case WAIT_OBJECTJ) + 0:
// завершился процесс, идентифицируемый h[0], т e описателем (hProcess1)
break;

case WATT_OBJECT_0 + 1:
// завершился процесс, идентифицируемый h[1], т e описателем (hProcess2)
break;

case WAIT_OBJECT_0 + 2:
// завершился процесс, идентифицируемый h[2], т. e описателем (hProcess3)
break;
}

Функции для работы с объектом Событие

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

Чаще всего вторичные потоки не могут обрабатывать сообщения, так как не создают окон. Это идеальный случай для применения объекта Событие (event object).События - самая примитивная разновидность объектов ядра. Они содержат счетчик числа пользователей (как и все объекты ядра) и две булевы переменные: одна сообщает тип данного объекта-события, другая — его состояние (свободен или занят).

Объект событие может быть либо установленным (свободным), либо сброшенным (занятым). Объект событие можно создать с помощью функции:

 

HANDLE event=CreateEvent(NULL,fManual,fInitial,szName);

 

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

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

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

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

 

SetEvent(event);

 

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

 

ResetEvent(event);

 

Для проверки того, установлено ли событие, функция потока обычно вызывает функцию WaitForSingleObject со вторым параметром, равным INFINITY:

 

 WaitForSingleObject(event, INFINITY);

 

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

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

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

Уведомление при помощи объекта Событие

Приведем примерные действия по использованию объекта событие.

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

 

 struct PARAM

 { BOOL type;   // тип выполняемых потоком действий

  HWND wnd;   // окно, в оконной процедуре которого создается поток

  BOOL stop;   // признак завершения работы потока

 HANDLE event;   // объект-событие, поток работает, если оно установлено

 };

 

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

 

 #define IDM_CIRCLES 1

 #define IDM_BARS 2

 #define IDM_STOP 3

 #define IDM_BEGIN 4

 . . .

 

 LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

 {

  static struct PARAM p; // используется как общая память потоков

  . . .

  switch(msg)

  {

   case WM_CREATE:

   { // создание изначально сброшенного объекта событие – третий

   // аргумент равен FALSE, при этом объект событие не будет

   // автоматически становиться занятым при выходе из функции

    // WaitForSingleObject – второй аргумент равен TRUE

    // (первый и посл. аргументы - для объектов, разделяемых процессами)

   p.event=CreateEvent(NULL,TRUE,FALSE,NULL);

 

   // запись в общую память потоков, создание и запуск потока,

    // который выполняется, если объект событие установлено

   p.type=0; p.wnd=hWnd; p.stop=0;

 

   _beginthread(ThreadFun,0,(void *)(&p));

  }; break;

 

   case WM_COMMAND: // обработка сообщений от меню

   { switch(LOWORD(wParam))

    {

     case IDM_BEGIN: // выполнение потока

     { // сделать события установленным

     SetEvent(p.event);

     }; break;

 

    case IDM_STOP: // приостановка потока

    { // сделать события сброшеным

     ResetEvent(p.event);

     }; break;

 

     case IDM_CIRCLES: // изменить тип действия

    { // записать тип выполняемого потоком действия

     p.type=0;

     }; break;

 

    case IDM_BARS: // изменить тип действия

     { // записать тип выполняемого потоком действия

     p.type=1;

     }; break;

   }

  }; return 0;

 

   case WM_DESTROY:

  { // записать признак завершения потока

   p.stop=1;

   PostQuitMessage(0);

  }; return 0l;

 

  // обработка других сообщений

  . . .

   default: return DefWindowProc(hWnd, msg, wParam, lParam);

  }

  return 0l;

 }

 

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

 

 void ThreadFun(void *lpvoid)

{

 // автоматические переменные потока

 . . .

 

 // получение адреса общей памяти потоков

  struct PARAM *p=(struct PARAM *)lpvoid;

 

 while(!p->stop) // пока признак завершения не установлен

 {

  // ожидание установки объекта события - если объект событие установлено,

  // поток будет работать, если сброшено, то поток будет ждать,

  // пока эта функция возвратит управление

   WaitForSingleObject(p->event,INFINITE);

 

  // выполнение действия, определяемого p->type, при этом может

  // использоваться дескриптор создавшего поток окна, он хранится в p->wnd

  . . .

 }

 

 _endthread(); // полная остановка потока

}

 

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

Семафоры

Объекты ядра «семафор» используются для учета ресурсов Как и все объекты ядра, они содержат счетчик числа пользователей, но, кроме того, поддерживают два 32 битных значения со знаком: одно определяет максимальное число ресурсов (контролируемое семафором), другое используется как счетчик текущего числа ресурсов

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

Для семафоров определены следующие правила:

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

Не путайте счетчик текущего числа ресурсов со счетчиком числа пользователей объекта-семафора

Объект ядра «семафор» создается вызовом CreateSemapbore 

HANDLE CreateSemaphore( PSECURITY_ATTRIBUTE psa, LONG lInitialCount, LONG lMaximumCount, PCTRTR pszName)

Любой процесс может получить свой («процессо-зависимый») описатель существующего объекта «семафор», вызвав OpenSemaphore 

HANDLE OpenSemaphore( DWORD fdwAccess, BOOL bInhentHandle, PCTSTR pszName);

Параметр lMaximumCount сообщает системе максимальное число ресурсов, обрабатываемое приложением Поскольку это 32-битное значение со знаком, пре дельное число ресурсов можетдостигать 2 147 483 647 Параметр lInitiа1Соипt указывает, сколько из этих ресурсов доступно изначально (на данный момент) При инициализяции серверного процесса клиентских запросов нет, поэтому я вызывается CreateSemaphore так

HANDLE hSem = CreateSemaphore(NULL, 0, 5, NULL);

Это приводит к созданию семафора со счетчиком максимального числа ресурсов равным 5, при этом изначально ни один ресурс не доступен (Кстати, счетчик числа пользователей данного объекта ядра равен 1, так как я только что создал этот объект, не запутайтесь в счетчиках) Поскольку счетчику текущего числа ресурсов присвоен 0 семафор находится в занятом состоянии А это значит, что любой поток, ждущий семафор, просто засыпает

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

Если Wait-функция определяет, что счетчик текущего числа ресурсов равен 0 (семафор занят), система переводит вызывающий поток в состояние ожидания Когда другой поток увеличит значение этого счетчика, система вспомнит о ждущем потоке и снова начнет выделять ему процессорное время (а он, захватив ресурс, уменьшит значение счетчика на 1).

Поток увеличивает значение счетчика текущего числа ресурсов, вызывая функцию ReleaseSemaphore 

BOOL ReleaseSemaphore( HANDLE hSem, LONG lReleaseCount, PLONG pPreviousCount);

Она просто складывает величину lReleaseCount со значением счетчика текущего числа ресурсов. Обычно в параметре lReleaseCount передают 1, но это вовсе не обязательно. Функция возвращает исходное значение счетчика ресурсов в *plPreviousCount.

Мьютексы

Объекты ядра «мьютексы» гарантируют потокам взаимоисключающий доступ к единственному ресурсу. Отсюда и произошло название этих объектов (mutual exclusion, mutex). Они содержат счетчик числа пользователей, счетчик рекурсии и переменную, в которой запоминается идентификатор потока. Мьютексы ведут себя точно так же, как и критические секции. Однако, если последние являются объектами пользовательского режима, то мьютексы — объектами ядра. Кроме того, единственный объект-мьютекс позволяет синхронизировать доступ к ресурсу нескольких потоков из разных процессов; при этом можно задать максимальное время ожидания доступа к ресурсу.

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

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

  •  если его идентификатор потока равен 0 (у самого потока не может быть та кой идентификатор), мьютекс не захвачен ни одним из потоков и находится в свободном состоянии;
  •  если его идентификатор потока не равен 0, мьютекс захвачен одним из потоков и находится в занятом состоянии;

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

HANDLE CreateMutex( PSECURITY_ATTRIBUTES psa, BOOL fIniLialOwner, PCTSTR pszName);

Любой процесс может получить свой («процессо-зависимый») описатель существующего объекта «мьютекс», вызвав OpenMutex: 

HANDLE OpenMutex( DWORD fdwAccess, 800L bInheritHandle, PCTSTR pszName);

Параметр fInitialOwner определяет начальное состояние мъютекса. Если в нем передается FALSE (что обычно и бывает), объект-мьютекс не принадлежит ни одному из потоков и поэтому находится в свободном состоянии. При этом его идентификатор потока и счетчик рекурсии равны 0 Если же в нем передается TRUE, идентификатор потока, принадлежащий мьютексу, приравнивается идентификатору вызывающего потока, а счетчик рекурсии получает значение 1. Поскольку теперь идентификатор потока отличен от 0, мьютекс изначально находится в занятом состоянии.

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

Если Wait-функция определяет, что у мьютекса идентификатор потока не равен 0 (мьютекс занят), вызывающий поток переходит в состояние ожидания. Система за поминает это и, когда идентификатор обнуляется, записывает в него идентификатор ждущего потока, а счетчику рекурсии присваивает значение 1, после чего ждущий поток вновь становится планируемым. Все проверки и изменения состояния объекта-мьютекса выполняются на уровне атомарного доступа.

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

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

BOOL ReleaseMutex(HANDLE hMutex);

Эта функция уменьшает счстчик рекурсии в объекте-мьютексе на 1. Если данный объект передавался во владение потоку неоднократно, поток обязан вызвать Release Mutex столько раз, сколько необходимо для обнуления счетчика рекурсии.

Отказ от объекта-мьютекса

Если какой-то посторонний поток попытается освободить мьютекс вызовом функции ReleaseMutex, то она, проверив идентифика торы потоков и обнаружив их несовпадение, ничего делать не станет, а просто вер нет FALSE. Тут же вызвав GetLastError, Вы получите значение ERROR_NOT_OWNER.

Мьютексы и критические секции

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

Характеристики

Объект-мьютекс

Обьект — критическая секция

Быстродействие

Малое

Высокое

Возможность использования за границами процесса

Да

Нет

Объявление

HANDLE hmfx; 

CRITICAL_SECTION cs; 

Инициализация

hmtx = CreateMutex (NULL, FALSE, NULL); 

InitializeCriticalSection(&cs); 

Очистка

CloseHandle(hmtx); 

DeleteCriticalSection(&cs); 

Бесконечное ожидание

WaitForSingleObject (hmtx, INFINITE); 

EnterCrittcalSection(&cs); 

Ожидание в течение 0 мс

WaitForSingleObject (hmtx, 0); 

TryEnterCriticalSection (&cs); 

Ожидание в течение произвольного периода времени

WaitForSingleObject (hmtx, dwMilliseconds); 

Невозможно

Освобождение

ReleaseMutex(hmtx); 

LeaveCriticalSecliun(&cs); 

Возможность параллельного ожидания других объектов ядра

Да (с помощью WaitForMultipleObjects или аналогичной функции)

Нет

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

Объект

Находится в занятом состоянии, когда

Переходит в свободное состояние, когда

Побочный эффект успешного ожидания

Процесс
Поток

процесс еще активен поток еще активен

процесс завершается (ExitProcess, TerminateProcess) 
поток завершается (ExitThread, TerminateThread) 

Нет
Нет

Объект

Находится в занятом состоянии, когда:

Переходит в свободное состояние, когда:

Побочный эффект успешного ожидания

Задание

время, выделенное заданию, еще не истекло

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

Нет

Файл

выдан запрос на ввод-вывод

завершено выполнение запроса на ввод-вывод

Нет

Консольный ВВОД

ввода нет

ввод есть

Нет

Уведомление об изменении файла

в файловой системе нет изменений

файловая система обнаруживает изменения

Сбрасывается в исходное состояние

Событие с автосбросом

вызывается ResetEvent, PulseEvent или ожидание успешно завершилось

вызывается SetEvent или PulseEvent

Сбрасывается в исходное состояние

Событие со сбросом вручную

вызывается ResetEvent или PulseEvent

вызывается SetEvent или PulseEvent

Нет

Ожидаемый таймер с автосбросом

вызывается CancelWaitable- Тiтеr или ожидание успешно завершилось

наступает время срабатывания (SetWaitableTimer) 

Сбрасывается в исходное состояние

Ожидаемый таймер со сбросом вручную

вызывается CancelWaitableTimer 

наступает время срабатывания (SetWaitableTimef)

Нет

Семафор

ожидание успешно завершилось

счетчик > 0 (ReleaseSemaphore) 

Счетчик уменьшается на 1

Мьютекс

ожидание успешно завершилось

поток освобождает мьютекс (ReleaseMutex) 

Передается пото ку во владение

Критическая секция (поль зовательского режима)

ожидание успешно завершилось ( (Try)EnterCriticalSection)

поток освобождает критическую секцию (LeaveCriticalSection)

Передается потоку во владение


 

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

54467. Microsoft Word 97 1 MB
  Порядковый номер видимой в окне страницы документа Номер раздела в котором находится видимая страница Номер видимой страницы общее число страниц в документе Расстояние от курсора ввода до верхнего края страницы Номер строки в которой находится курсор Номер позиции курсора в строке Индикатор режима записи макрокоманды Индикатор режима редакторской правки Индикатор режима расширения маркировки Индикатор режима замены Режимы отображения документа...
54468. Microsoft Word 2000 2.67 MB
  Порядковый номер видимой в окне страницы документа Номер раздела в котором находится видимая страница Номер видимой страницы общее число страниц в документе Расстояние от курсора ввода до верхнего края страницы Номер строки в которой находится курсор Номер позиции курсора в строке Индикатор режима записи макрокоманды Индикатор режима редакторской правки Индикатор режима расширения маркировки Индикатор режима замены Индикатор языка Режимы...
54469. Квадратні корені. Перетворення ірраціональних виразів 9.85 MB
  Обладнання: мультимедійний проектор презентація уроку Квадратні корені. Перетворення ірраціональних виразів презентація Історія математичних позначень презентація Багатозначні терміни. Оголошення представлення теми та очікуваних результатів слайди № 1-4 Надання необхідної інформації слайд № 5 Інтерактивна вправа...
54470. Математичне моделювання 39.5 KB
  Усні вправи: Якими математичними поняттями зручно змоделювати зображені предмети з навколишнього середовища слайди № 11-16 Розв’язування прикладних задач слайди № 23-24 Коментарі до розв’язування задач Задача 1. Дана задача прикладна бо в ній говориться про поверхню підлоги – нематематичне поняття. слайд № 25 Задача 2. Це також прикладна задача з практичним змістом.
54471. Використання та розробка мультимедійних ресурсів для навчання школярів 118 KB
  Тому зробимо спробу розібратися в тому що представляє собою урок з використанням мультимедійних засобів навчання та як можна залучити учнів до створення мультимедійних ресурсів сучасного уроку мови та літератури. Розпочинаються вони з того що зміст мультимедійного уроку припускає наявність певного пакету: мультимедійну презентацію; сценарій уроку що деталізує виведення та шляхи реалізації навчальних завдань опис технологічних прийомів; наявність відповідного до уроку дидактичного матеріалу; контрольновимірювальні матеріали що...
54472. Музыка в баснях 47 KB
  Цели:организовать деятельность учащихся на ознакомление с образцами стихотворных литературных произведений о музыке; помочь учащимся целостно представить внутреннюю связь музыки с литературой; создать содержательные и организационные условия для привития навыков восприятия музыки разного характера, умению их анализировать...
54473. Музыка и искусство слова 113 KB
  Цель урока: организовать деятельность учащихся на углубление знаний о роли литературы в музыкальном искусстве; обеспечить применение учащимися знаний о жанрах в литературе и музыке, их взаимосвязи, интонационных режимах, музыкальных произведениях; содействовать осознанию учащимися целостного представления о влиянии литературы на музыкальное искусство; содействовать развитию у детей умения общаться;...
54474. Могут ли музыка и литература жить друг без друга? 42 KB
  Цели: направить деятельность учащихся на обобщение представления о многообразии и богатстве связей между музыкой и литературой их взаимовлиянии и взаимодополнении; содействовать развитию представления школьников о специфике музыкально-образного отражения мира; способствовать формированию интонационно-слухового опыта учащихся развитию вокально-хоровых навыков; способствовать формированию музыкальной культуры учащихся как составляющей духовной культуры; продолжить использовать...
54475. Музыка в рассказах 136 KB
  Верите ли вы в счастье Что такое счастье Приведите примеры посетившего вас счастья Д. А что для всех нас будет общим счастьем на уроке музыкального искусства Д. Правильно воплощение мечты явилось счастьем для мышонка. А для меня счастье – видеть ваши умные глаза добрые лица отзывчивые сердца и искренние улыбки.