Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шпора 35 стр [25 вопросов].doc
Скачиваний:
45
Добавлен:
15.06.2014
Размер:
384.51 Кб
Скачать

Переключение потоков

Функция SwitchToThreadпозволяет подключить к процессору другой поток (если он есть):

BOOL SwitchToThread();

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

SwitchToThreadпозволяет потоку, которому не хватает процессорного времени, отнять этот ресурс у потока с более низким приоритетом. Она возвращает FALSE, если на момент ее вызова в системе нет ни одного потока, готового к исполнению, в ином случае — ненулевое значение.

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

Приоритет

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

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

Пока в системе имеются планируемые потоки с приоритетом 31, ни один поток с более низким приоритетом процессорного времени не получает. Такая ситуация называется "голоданием* (starvation). Она наблюдается, когда потоки с более высоким приоритетом так интенсивно используют процессорное время, что остальные практически не работают. Вероятность этой ситуации намного ниже в многопроцессорных системах, где потоки с приоритетами 31 и 30 могут выполняться одновременно. Система всегда старается, чтобы процессоры были загружены работой, и они простаивают только в отсутствие планируемых потоков.

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

А теперь обратите внимание на еще один момент. Потоки с более высоким приоритетом всегда вытесняют потоки с более низким приоритетом независимо оттого, исполняются последние или нет. Допустим, процессор исполняет поток с приоритетом 5, и тут система обнаруживает, что поток с более высоким приоритетом готов к выполнению. Что будет? Система остановит поток с более низким приоритетом — даже если не истек отведенный ему квант процессорного времени — и подключит к процессору поток с более высоким приоритетом (и, между прочим, выдаст ему полный квант времени),

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

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

Все потоки в системе должны иметь доступ к системным ресурсам — кучам, последовательным портам, файлам, окнам и т.д. Если один из потоков запросит монопольный доступ к какому-либо ресурсу, другим погокам, которым тоже нужен этот ресурс, не удастся выполнить свои задачи. А с другой стороны, просто недопустимо, чтобы потоки бесконтрольно пользовались ресурсами. Иначе может получиться так, что один поток пишет в блок памяти, из которого другой что-то считывает.

Потоки должны взаимодействовать друг с другом в двух основных случаях:

  • совместно используя разделяемый ресурс (чтобы не разрушить его);

  • когда нужно уведомлять другие потоки о завершении каких-либо операций.

    1. Синхронизация процессов и потоков. Mutex. Event. Semaphore. Critical Section. Таймер. Сходство и различие. Способы создания и использования.

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

Большая часть синхронизации потоков связана с атомарным доступом(atomic access) — монопольным захватом ресурса обращающимся к нему потоком.

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

LONG InterlockedExchangeAdd( PLONG plAddend, LONG lIncrement);

На компьютерах с процессорами семейства x86эти функции выдают по шине аппаратный сигнал, не давая другому процессору обратиться по тому же адресу памяти. На платформе AlphaInterlocked-функции действуют примерно так:

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

  2. Считывают значение из памяти в регистр.

  3. Изменяют значение в регистре.

  4. Если битовый флаг сброшен, повторяют операции, начиная с п. 2. В ином случае значение из регистра помещается обратно в память.

Вот еще две функции из этого семейства: InterlockedExchangeиInterlockedExchangePointerмонопольно заменяют текущее значение переменной типа LONG, адрес которой передается в первом параметре, на значение, передаваемое во втором параметре. В 32-разрядпом приложении обе функции работают с 32-разрядными значениями, но в 64-разрядной программе первая оперирует с 32-разрядными значениями, а вторая — с 64-разрядными.

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

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES Ipsa, BOOL flnitialOwner LPTSTR IpszMutexName)

Параметр Ipsa указывает на структуру SECURITY_ATTRIBUTES. Параметр flnitialOwner определяет, должен ли поток, создающий мьютекс, быть первоначальным владельцем этого объекта. Если он равен TRUE, данный поток становится его владельцем, и, следовательно, объект-мьютекс оказывается в занятом состоянии. Любой другой поток, ожидающий данный мьютекс, будет приостановлен, пока поток, создавший этот объект, не освободит его. Передача FALSE в параметре flnitialOwner подразумевает, что объект-мьютекс не принадлежит ни одному из потоков и поэтому «рождается свободным». Первый же поток из числа ожидающих этот объект может занять его и тем самым продолжить свое выполнение.

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

Одно из главных отличий объектов-мьютексов от критических секций в том, что первые способны синхронизировать потоки, выполняемые в разных процессах. С этой целью поток в каждом процессе должен располагать своим, специфичным для данного процесса описателем единственного объекта-мьютекса. Эти описатели можно получить несколькими способами. Самый распространенный: один из потоков каждого процесса вызывает CreateMutex, и все передают ей через параметр IpszMutexName идентичные строки. Первый вызов функции приводит к созданию объекта ядра «мьютекс», остальные вызовы просто возвращают соответствующим потокам описатели этого объекта, значения которых специфичны для каждого процесса.

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

Еще один способ получить описатель мьютекса — вызвать OpenMutex:

HANDLE OpenMutex(DWORD fdwAccess BOOL flnhent LPTSTR IpszName)

Параметр fdwAccess может быть равен либо SYNCHRONIZE, либо MUTEX_ALL_ACCESS. Параметр flnhent определяет, унаследует ли дочерний процесс данный описатель данного объекта-мьютекса. А параметр IpszName — это имя объекта-мьютекса в виде строки с нулевым символом в конце.

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

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

BOOL ReleaseMutex(HANDLE hMutex)

Эта функция переводит объект-мьютекс из занятого состояния в свободное, т. е. делает примерно то же, что и LeaveCriticalSection по отношению к критическим секциям. Однако надо учитывать один важный момент: ReleaseMutex воздействует на мьютекс, только если поток, вызвавший ее, владеет этим объектом. Сразу за этим вызовом ReleaseMutex любой поток, ожидающий объект-мьютекс, может захватить его и приступить к исполнению. Конечно, когда поток захватывает мьютекс, тот вновь переходит в занятое состояние. Если мьютекс не нужен ни одному потоку, он остается в свободном состоянии, указывая тем самым, что к защищенным данным никто не обращается. А появится поток, которому нужен этот объект, — он тут же захватит мьютекс и заблокирует доступ к нему прочим потокам.

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

Однако система не терпит подобных ситуаций и, заметив, что произошло, автоматически переводит мьютекс в свободное состояние. Поэтому потоки, ожидающие данный объект через WaitForSingleObject, получают возможность захватить его, а упомянутая функция возвращает WAIT_ABANDONED вместо WAIT_OBJECT_0. Таким образом, поток узнает, что мьютекс освобожден некорректно. Это обычно указывает на то, что в исходном коде программы есть «жучок». Выяснить, что сделал с защищенными данными завершенный поток — владелец объекта-мьютекса, увы, невозможно.

И последнее. С объектом-мьютексом сопоставляется счетчик, фиксирующий, сколько раз данный объект передавался во владение какому-либо потоку. Поэтому, если поток вызовет WaitForSingleObject для уже принадлежащего ему объекта-мьютекса, он сразу же получит доступ к защищаемым этим объектом данным (так как система — по упомянутому счетчику — тут же определит, что поток уже владеет этим объектом-мьютексом). Кроме того, при каждом вызове WaitForSingleObject потоком — владельцем объекта-мьютекса этот счетчик увеличивается на 1. А значит, чтобы перевести мьютекс в свободное состояние, потоку придется соответствующее число раз вызывать ReleaseMutex. Функции EnterCritical-Section и LeaveCriticalSection действуют по отношению к критическим секциям аналогичным образом.

Event

События — самая примитивная разновидность синхронизирующих объектов, резко отличающаяся от семафоров и мьютексов. Последние обычно применяются для контроля за доступом к данным, а события просто уведомляют об окончании какой-либо операции. Объекты «событие» бывают двух типов: со сбросом вручную (manual-reset events) и с автосбросом (auto-reset events). Первые позволяют уведомлять об окончании операции сразу несколько потоков, вторые — только один поток.

События обычно используют в том случае, когда какой-то поток выполняет инициализацию, а затем сигнализирует другому потоку, что тот может продолжить работу. Инициализирующий поток переводит объект «событие» в занятое состояние и приступает к своим операциям. По окончании инициализации поток сбрасывает событие в свободное состояние. В то же время рабочий поток приостанавливает свое исполнение и ждет перехода события в свободное состояние. Как только инициализирующий поток освободит событие, рабочий поток «проснется» и продолжит работу.

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

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

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES Ipsa, BOOL fManualReset BOOL fInitialState, LPTSTR IpszEventName),

Параметр fManualReset (Булева переменная) сообщает системе, какое событие Вы хотите создать: со сбросом вручную (TRUE) или с автосбросом (FALSE). Параметр flnitialState определяет начальное состояние события: свободное (TRUE) или занятое (FALSE). После того как система создает объект «событие», CreateEvent возвращает описатель события, специфичный для конкретного процесса. Потоки из других процессов могут получить доступ к этому объекту: 1) вызовом CreateEvent с тем же параметром IpszEventName; 2) наследованием описателя; 3) применением DuplicateHandle; и 4) вызовом OpenEvent с передачей в параметре IpszEventName имени, совпадающего с тем, что было указано в аналогичном параметре функции CreateEvent. Вот что представляет собой OpenEvent

HANDLE OpenEvent(DWORD fdwAccess BOOL fInherit LPTSTR IpszName),

Ну а закрытие событий осуществляется весьма популярной функцией CloseHandle.

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

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

В отношении событий со сбросом вручную дело обстоит совершенно иначе. У Вас может быть несколько потоков, ждущих возникновения одного события. Когда оно наконец происходит, каждый из ожидавших потоков получает возможность выполнить свои операции. Чтобы лучше разобраться в этом механизме, вернемся к примеру с чтением и обработкой файловых данных. Допустим, один поток отвечает за считывание данных из файла в буфер. После того как данные считаны, Вы запускаете девять других потоков. Каждый из них обрабатывает данные по-своему. Предположим, в файле находится документ, созданный в каком нибудь текстовом процессоре. Тогда пусть первый поток подсчитывает символы, второй — слова, третий — страницы, четвертый, скажем, проверяет орфографию, пятый печатает документ и т. п. Отметьте — и это крайне важно, — что у всех потоков есть общая черта: ни один из них ничего не записывает в файл. Данные для них — ресурс, открытый только для чтения. Ясно, что при возникновении события все ожидающие его потоки должны возобновить свое выполнение. Вот Вам и один из случаев применения событий со сбросом вручную. Когда объект «событие со сбросом вручную» переходит в свободное состояние, система разрешает исполнение всех ожидавших его потоков. Поток переводит такой объект «событие» в свободное состояние вызовом SetEvent

BOOL SetEvent(HANDLE hEvent)

Эта функция принимает описатель объекта «событие» и меняет состояние объекта на свободное. Если операция прошла успешно, функция возвращает TRUE. После перевода объекта в свободное состояние он пребывает в нем, пока какой-нибудь поток явным образом (т. е «вручную») не сбросит событие, вызвав:

BOOL ResetEvent(HANDLE hEvent),

Эта функция принимает описатель объекта «событие» и переводит объект в занятое состояние. Если операция прошла успешно, функция возвращает TRUE. (Я же говорил, что события — самые примитивные из синхронизирующих объектов.)

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

Да, и вот еще что. Как поток, считывающий данные из файла, узнает, что пора считывать следующий блок? Нам известно, что это должно произойти по окончании работы прочих потоков с текущей порцией данных. Значит, и эти потоки должны как-то сигнализировать об окончании своих операций. Лучше всего создать в каждом из них собственный объект «событие», а описатели этих объектов поместить в массив; тогда поток, отвечающий за чтение данных, сможет воспользоваться функцией WaitForMultipleObjects и сообщить ей. что он собирается ждать освобождения всех девяти объектов.

Поскольку в программистской практике нередко после вызова SetEvent объект «событие» освобождается и тут же следует вызов ResetEvent, в Win32 предусмотрена дополнительная функция, способная самостоятельно выполнять все три операции:

BOOL PulseEvent(HANDLE hEvent),

После возврата управления функцией PulseEvent событие остается в занятом состоянии. Если функция выполнена успешно, возвращается TRUE.

Семафоры

Объекты ядра «семафор» используются для учета ресурсов. Они позволяют потоку запрашивать число доступных ресурсов. Если один или более ресурсов свободны, счетчик доступных ресурсов после запроса уменьшается на 1. Семафоры осуществляют такую операцию на атомном уровне. Иными словами, когда Вы запрашиваете у семафора ресурс, операционная система сама проверяет, свободен ли данный ресурс, и уменьшает счетчик доступных ресурсов, исключая вмешательство другого потока. Только после уменьшения счетчика доступных ресурсов система разрешает другому потоку запрашивать какой-либо ресурс.

Допустим, у компьютера 3 последовательных порта, их могут использовать не более трех потоков одновременно, и каждый порт можно закрепить за одним потоком. Эта ситуация — отличная возможность применить семафор. Чтобы отслеживать занятость последовательных портов, Вы создаете семафор со счетчиком, равным 3 (ведь последовательных портов у Вас именно 3). При этом нужно учесть, что семафор считается свободным, если его счетчик больше 0, и занятым, если счетчик равен 0 (меньше 0 он быть не может). При каждом вызове из потока WaitForSingleObject с передачей ей описателя семафора система проверяет: больше ли 0 счетчик ресурсов у данного семафора? Если да, уменьшает счетчик на 1 и «будит» поток. Если при вызове WaitForSingleObject счетчик семафора равен 0, система оставляет поток неактивным до того, как другой поток освободит семафор (т. е. увеличит его счетчик ресурсов).

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

Семафор создается вызовом CreateSemaphore:

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTE Ipsa, LONG cSemlnitial LONG cSemMax LPTSTR IpszSemName)

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

Последний параметр функции, IpszSemName, присваивает семафору имя в виде строки. В дальнейшем это имя позволит получить описатель семафора из других процессов с помощью CreateSemaphore или OpenSemaphore:

HANDLE OpenSemaphore(DWORD fdwAccess BOOL flnhent LPTSTR IpszName)

По семантике эта функция идентична уже рассмотренной OpenMutex.

Чтобы перевести семафор в свободное состояние (увеличить его счетчик ресурсов), вызовите ReleaseSemaphore:

BOOL ReleaseSemaphore(HANDLE hSemaphore LONG cRelease LPLONG IplPrevious)

Она похожа на ReleaseMutex, но имеет ряд отличий. Во-первых, любой поток может вызвать ее когда угодно, поскольку объекты «семафор» не принадлежат лишь какому-то одному потоку. Во-вторых, с ее помощью счетчик ресурсов можно увеличивать более чем на 1 единовременно. Параметр cRelease как раз и определяет, какими порциями должен освобождаться семафор. Например, у нас есть программа, копирующая данные из одного последовательного порта в другой. Она должна дважды запрашивать ресурсы у семафора, соответ ственно дважды вызывая WaitForSmgleObject. Но освободить оба ресурса программа может через один вызов RekaseSemaphore. Вот как это делается:

// получаем два последовательных порта WaitForSingleObject(g_hSemSerialPort INFINITE) WaitForSingleObject(g_hSemSenalPort INFINITE)

// чего-то делаем с ними

// освобождаем последовательные порты чтобы и другие // приложения могли ими попользоваться ReleaseSemaphore(g_hSemSenalPort 2 NULL),

Было бы неплохо, если бы мы могли вместо двухкратного вызова WaitForSingleObject один раз обратиться к WaitForMultipleObjects. Но последняя не разрешает в одном вызове дважды использовать одинаковый описатель. Утешьтесь тем, что хоть счетчик семафора можно сразу увеличить до нужного значения.

Последний параметр функции ReleaseSemaphore — IplPrevious — указатель на переменную типа LONG, в которой возвращается значение счетчика ресурсов, предшествующее тому, что получилось после его увеличения на cRelease. Если Вас не интересует это значение, передайте в параметре IplPrevious значение NULL.

Было бы удобнее работать с Win32-функцией, способной определять у семафора состояние счетчика, не меняя его значения. Поначалу я думал, что вызовом ReleaseSemaphore с передачей ей во втором параметре нуля можно узнать истинное значение счетчика в переменной типа LONG, на которую указывает параметр IplPrevious. Но не вышло; функция занесла туда 0. Я передал во втором параметре заведомо большее число, и — тот же результат. Тогда мне стало ясно: получить значение счетчика, не изменив его, невозможно.

Критические секции.

Критическая секция (critical section) — это небольшой участок кода, требующий монопольного доступа к каким-то общим данным. Среди синхронизирующих объектов критические секции наиболее просты, но применимы для синхронизации потоков лишь в пределах одного процесса. Они позволяют сделать так, чтобы единовременно только один поток получал доступ к определенному региону.

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

Создание

Для этого сначала сформируем в своем процессе структуру данных CRITICAL_SECTION. Она должна быть глобальной, чтобы к ней могли обращаться разные потоки. Обычно критические секции представляют собой набор глобальных переменных. Хотя структура CRITICAL_SECTION и ее элементы определены в файле WINNT.H, считайте ее содержимое «черным ящиком». Win32-функции, управляющие критическими секциями, сами инициализируют и модифицируют элементы данной структуры. Вам же этого делать не следует.

Применение

Прежде чем синхронизировать потоки с помощью критической секции, нужно инициализировать ее вызовом InitializeCriticalSection, передав адрес структуры CRITICAL_SECTION в параметре IpCriticalSection:

VOID InitializeCriticalSection(LPCRITICAL_SECTION IpCnticalSection)

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

VOID EnterCriticalSection(LPCRITICAL_SECTION IpCriticalSection)

LeaveCriticalSection(&g_CnticalSection)

Применение критических секций позволяет организовать поочередное обращение двух потоков к каким-то общим данным. Но в некоторых случаях существует вероятность одновременного обращения к общим данным и большего числа потоков. Тогда каждый из них должен предварительно вызвать EnterCriticalSection. Если один поток уже захватил критическую секцию, то ожидающие доступа к этой секции засыпают. Когда поток освободит критическую секцию, вызвав LeaveCriticalSection, система «разбудит» только одного ожидающего, отдав ему права на эту секцию. Другие спящие потоки останутся в том же состоянии.

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

По завершении программы все переменные типа CRITICAL_SECTION нужно удалить вызовом DeleteCriticalSection:

VOID DeleteCriticalSection(LPCRITICAL_SECTION IpCriticalSection)

Она освобождает все ресурсы, включенные в критическую секцию. И, естественно, функции EnterCriticalSection и LeaveCriticalSection нельзя вызывать, используя удаленную переменную типа CRITICAL_SECTION, пока ее снова не инициализируют функцией InitializeCriticalSection. Кроме того, нельзя допускать удаления критической секции в тот момент, когда какой-либо поток вызвал для нее EnterCriticalSection.

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

BOOL TryEnterCriticalSection(LPCRITICAL_SECTION IpCriticalSection),

Если при вызове TryEnterCriticalSection указанная критическая секция не занята никаким потоком, включая вызывающий, эта функция передает права на критическую секцию вызвавшему потоку и возвращает TRUE. Если же критическая секция уже занята, функция немедленно возвращает FALSE. Различие между EnterCriticalSection и TryEnterCriticalSection в том, что последняя функция не приостанавливает выполнение потока.

В Windows 95 TryEnterCriticalSection не реализована. Ваша программа не будет работать в Windows 95, если использует эту функцию.

Таймер

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

Назначение:

  • Использование в программах, индицирующих время.

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

  • Поддержка обновления информации о состоянии

  • Реализация "автосохранения"

  • Завершение демонстрационных версий программ

  • Задание темпа изменения

  • Мультимедиа

Можно присоединить таймер к программе при помощи вызова функции SetTimer. Функция SetTimer содержит целый параметр, задающий интервал, который может находиться в пределах от 1 до 4 294 967 295 миллисекунд. Это значение определяет темп, с которым Windows посылает программе сообщения WM_TIMER. Если в программе есть таймер, то она вызывает функцию KillTimer для остановки потока сообщений от таймера. Можно запрограммировать "однократный" таймер, вызывая функцию KillTimer при обработке сообщения WM_TIMER. Вызов функции KillTimer очищает очередь сообщений от всех необработанных сообщений WM_TIMER. После вызова функции KillTimer программа никогда не получит случайного сообщения WM_TIMER. Сообщения WM_TIMER не являются асинхронными. Windows обрабатывает сообщения WM_TIMER во многом также, как сообщения WM_PAINT. Оба эти сообщения имеют низкий приоритет, и программа получит только их, если в очереди нет других сообщений. Сообщения WM_TIMER похожи на сообщения WM_PAINT и в другом смысле: Windows не хранит в очереди сообщений несколько сообщений WM_TIMER. Вместо этого Windows объединяет несколько сообщений WM_TIMER из очереди в одно сообщение. Сообщения WM_TIMER только информируют программу, когда ей следует обновить данные.

Если нужен таймер для измерения продолжительности работы программы, нужно вызвать SetTimer из функции WinMain или при обработке сообщения WM_CREATE, а KillTimer в ответ на сообщение WM_DESTROY. Установка таймера в функции WinMain обеспечивает простейшую обработку ошибки, если таймер недоступен.