- •Методические рекомендации
- •План лекции
- •Интерфейс графического устройства
- •Программирование, основанное на ресурсах
- •Управления памятью
- •Документы и их представление
- •Контрольные вопросы
- •Редактирование функций.
- •Изучение работы приложения.
- •Самостоятельная работа.
- •Добавление своего класса.
- •Домашняя работа.
- •Лекция 2. Основы программирования. Работа с классом Вид. Методические рекомендации:
- •Интерфейс графического устройства
- •Работа с gdi объектами
- •Пример создания нового gdi объекта
- •Режимы преобразования координат
- •Режимы преобразования координат с постоянным масштабом
- •Режимы преобразования координат с переменным масштабом
- •Замечание
- •Координатные пространства mfc
- •Пример использования режимов преобразования координат
- •Работа с окнами, содержащими полосы прокрутки
- •Линейки прокрутки
- •Различные способы прокрутки
- •Прием ввода с клавиатуры
- •Контрольные вопросы
- •Обработка сообщений от мыши.
- •Задание для самостоятельной домашней разработки.
- •Лекция 3. Меню. Панели инструментов и строки состояния Методические рекомендации
- •Меню Windows
- •Обновление командного пользовательского интерфейса
- •Класс cMenu
- •Создание контекстных меню
- •Панели элементов управления и каркас приложений
- •Панель инструментов
- •Растровое изображение панели инструментов
- •Состояния кнопок Любая кнопка может находиться в следующих состояниях
- •Всплывающие подсказки
- •Строка состояния
- •Определение секций в строке состояния
- •Строка сообщений
- •Индикатор состояния
- •Управление строкой состояния
- •Контрольные вопросы:
- •Добавление переменных-членов.
- •Добавление функции OnMouseMove.
- •Программирование команд контекстного меню.
- •Программирование команд главного меню.
- •Домашняя работа.
- •Лекция 4. Диалоговые окна и стандартные элементы управления Методические рекомендации
- •Работа с модальным диалоговым окном
- •Стандартные элементы управления
- •Работа с немодальными диалоговыми окнами
- •Пользовательские сообщения
- •Принадлежность диалогового окна
- •Контрольные вопросы
- •Создание класса “диалогового окна”.
- •Добавление переменных-членов класса вашего диалогового окна.
- •Написание инициализирующего кода
- •Присваивание переменным начальных значений.
- •Самостоятельная работа.
- •Домашнее задание.
- •Лекция 5. Обработка сообщений Windows и программирование многопоточных приложений Методические рекомендации
- •Обработка сообщений в однопоточной программе
- •Передача управления
- •Таймеры
- •Обработка в периоды простоя
- •Программирование многопоточных приложений
- •Функция рабочего потока и запуск потока
- •Общение основного потока с рабочим
- •Общение рабочего потока с основным
- •Синхронизация потоков с использованием событий
- •Блокировка потоков
- •Критические секции
- •Потоки пользовательского интерфейса
- •Контрольные вопросы
- •Домашнее задание.
- •Задание для самостоятельной работы
- •Управление процессом Пример 1.
- •2. Замена образа процеса Пример 2. Использование функции exec.
- •Пример 3. Использование неименованного канала.
- •Пример 4. Создание именованного канал с именем "fifo".
- •3. Сигналы.
- •Пример 5. Использование сигналов.
- •Пример 6. Сообщения.
- •Пример 7. Сообщения.
- •Пример 8. Разделение памяти.
- •Пример 9. Использование семафоров.
- •Пример 10. Создание процесса вWindows.
- •Пример 11. Использование неименованного канала.
- •Пример 12. Использование именованного канала.
- •Пример 13. Использование разделяемой памяти File Mapping.
- •Пример 14. Использование Mailslot.
- •Пример 15. Использование событий.
Программирование многопоточных приложений
Потоки в Windows бывают двух видов рабочие потоки (worker threads) и потоки пользовательского интерфейса (user-interface threads). MFC-библиотека поддерживает оба вида. У потока пользовательского интерфейса есть окна, а, значит, и свой цикл выборки сообщений, а у рабочего — нет. Рабочие потоки легче программировать, и они, как правило, полезнее. Не забывайте, что даже в однопоточном приложении есть поток, который называется основным потоком (main thread). Здесь важно помнить, что приложение — это процесс, содержащий как минимум один поток. |
Функция рабочего потока и запуск потока
Для выполнения длительных вычислений рабочий поток эффективнее обработчика сообщений, содержащего вызов PeekMessage. Однако прежде чем думать о запуске рабочего потока, надо написать для него глобальную функцию. Она должна возвращать значение типа UINT и принимать в качестве параметра одно 32-разрядное значение, объявленное как LPVOID. При запуске потока через этот параметр можно передать ему все, что угодно. Поток выполняет свои вычисления и завершается, когда глобальная функция возвращает управление. Он завершается и при закрытии процесса, но лучше, чтобы рабочий поток завершался раньше — это поможет избежать утечек памяти. Чтобы запустить поток (с функцией, скажем, ComputeThreadProc), программа делает вызов: CWinThread* pThread = AfxBeginThread(ConiputeThreadProc, GetSafeHwndO, THREAD_PRIORITY_NORMAL). Код функции потока выглядит так: UINT ComputeThreadProc (LPVOID pParam) { // процесс обработки return О; } Функция AfxBeginThread возвращает указатель на только что созданный объект “поток”. Этот указатель можно использовать для приостановки и возобновления исполнения потока (CWinThread::SuspendTbread и ResumeThread), но у объекта “поток” нет функции-члена для уничтожения потока. Второй параметр AfxBeginThread — 32-разрядное значение, передаваемое глобальной функции, а третий параметр представляет собой код приоритета потока. После запуска рабочего потока оба потока исполняются независимо друг от друга Windows распределяет время между ними (и потоками других процессов) в соответствии с их приоритетами. |
Общение основного потока с рабочим
Основной поток (ваша программа) может передавать информацию вспомогательному рабочему потоку разными способами. Однако отправка Windows-сообщения — один из способов, который работать не будет, так как у рабочего потока нет цикла выборки сообщений. Простейшее средство коммуникации — глобальная переменная, поскольку все глобальные переменные доступны всем потокам процесса. Допустим, рабочий поток в процессе вычислений увеличивает и проверяет значение глобальной целочисленной переменной, завершаясь, когда значение переменной достигает 100. Основной поток может принудительно завершить рабочий поток, присвоив глобальной переменной значение 100 или более.
Приведенный ниже код, на первый взгляд, должен работать, и, если Вы его протестируете, может быть, так и случится:
UINT ComputeThreadProc (LPVOID pParam)
{
g_nCount = 0;
while (g_nCo<jnt++ < 100)
{
// здесь выполняются какие-то вычисления
}
return 0;
}
Однако здесь есть одна проблема, которую можно обнаружить, лишь посмотрев на сгенерированный ассемблерный код. Значение g_nCount загружается в регистр, увеличивается там и переписывается обратно в g_nCount. Предположим, g_nCount равно 40 и Windows прерывает рабочий поток сразу же после того, как он загружает это значение в регистр. Теперь управление получает основной поток и присваивает g_nCount значение 100. При возобновлении рабочий поток увеличивает значение регистра и записывает обратно в g_nCount число 41, стирая предыдущее значение 100. И вот — цикл потока не завершается!
Если же мы включим оптимизацию кода при компиляции, то получим дополнительную проблему. Для переменной g_nCount компилятор использует регистр, причем значение переменной остается загруженным в него на протяжении всего цикла. Если основной поток изменит значение g_nCount в памяти, это никак не повлияет на цикл вычислений в рабочем потоке.
Однако, чтобы компилятор не хранил счетчик в регистре, можно объявить g_nCount как volatile. |
Перепишем процедуру потока следующим образом: UINT ConiputeThreadProc(LPVOID pParam) { g_nCount = 0; while (g_nCount < 100) { // здесь выполняются какие-то вычисления : :InterlockeclIncrenient((long*)&g_nCount); } return 0; } |
Функция Interlockedlncrement предотвращает обращение к переменной со стороны другого потока во время ее изменения. Теперь основной поток сможет завершить рабочий. |
Итак, Вы познакомились с некоторыми ловушками, подстерегающими программиста при использовании глобальных переменных.