Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Разработка приложений на Visual C++ с использов...doc
Скачиваний:
2
Добавлен:
01.05.2025
Размер:
375.81 Кб
Скачать

Лабораторная работа № 10 создание многопоточных приложений.

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

Общие положения

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

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

Многозадачность заключается в распределении процессорного времени между потоками, но не между процессами. Процессы и потоки в Windows реализованы как объекты. Класс процесса (Idle, Normal, High, Realtime) определяет базовый приоритет для всех создаваемых потоков.

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

Библиотека MFC и многопоточность

Библиотека классов MFC обеспечивает разработчиков полным набором средств создания процессов и потоков. Основной класс приложения CWinApp, являясь производным от класса CWinThread, представляет основной поток процесса приложения. Каждый поток для приложения, созданного на базе классов библиотеки MFC, представлен объектом класса CWinThread.

Библиотека MFC определяет два типа потоков:

  • Интерфейсные потоки (user-interface threads). Потоки этого типа предоставляют возможность взаимодействия с ними при помощи механизма сообщений и имеют в своем составе функции, аналогичные тем, которые создаются для объекта-приложения. Главный поток приложения — интерфейсный поток.

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

Собственно Windows не делает никаких различий между этими двумя видами потоков.

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

Чтобы получить доступ к объектам класса CWinThread можно использовать элементы:

HANDLE CWinThread ::m_hThread дескриптор потока,

DWORD CWinThread::m_nThreadID – идентификатор потока.

Таблица функций работы с потоками

Функция

Класс CWinThread

Примечание

Создание потока

::AfxBeginThread

::CreateThread

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

Сначала создается объект класса CWinThread,

а затем вызовом CreateThread – поток.

Завершение потока

::AfxEndThread.

Завершить текущий поток.

Извне поток можно прервать только при помощи функции Win32 API ::TerminateThread.

Управление методом удаления объекта класса CWinThread

::m_bAutoDeLete

Значение этого члена класса CWinThread по умолчанию равно TRUE. Если значение этого элемента равно FALSE, следует самостоятельно разрушить объект класса, когда необходимость в нем отпадет.

Получение и и назначения относительного приоритета потока.

::GetThreadPriority,

::SetThreadPriority

Могут использовать рабочие и интерфейсные потоки.

Приостановка и и возобновление потока

::SuspendThread,

::ResumeThread

Приостановить поток.

Возобновить поток.

Функция

Класс CWinThread

Примечание

Инициализация потока

::InitInstance

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

Определение кода завершения потока

::ExitInstance

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

Основная функция потока

::Run

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

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

Класс MFC

Объект Windows или функция Win32 API

Примечание

CSyncObject

Базовый класс для всех классов синхронизирующих

объектов

CCriticalSection

Критическая секция

CMutex

Взаимоисключение

CEvent

Событие

CSemaphore

Семафор

CSingleLock

::WaitForSingleObject

::WaitForSingleObjectEx

Ожидание единичного события

CMultiLock

::WaitForMultipleObjects

::WaitForMultipleObjectsEx

::MsgWaitForMultipleObjects

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

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

Задание №1. Создать одно-документное приложение MultiRect, выполнив следующие действия:

  1. С помощью мастера AppWizard построить остов приложения с одно-документным интерфейсом, исключив использование инструментов(ToolBar). Задать имя проекта - MultiRect и выбрать для проекта подходящий рабочий каталог.

  1. Настроить главное меню приложения. Меню должно содержать File и Help.

В File включить следующие пункты меню Stop(остановить) и Resume(возобновить).

Описание пункта меню Stop следующее:

Menu Item Properties\General: ID – ID_STOP; Caption - Stop.

Описание пункта меню Resume следующее:

Menu Item Properties\General: ID – ID_RESUME; Caption - Resume.

  1. Добавить в класс приложения CMultirectApp(файл MultirectApp.h) описание функции CancelThreads и переменной m_nDone:

// Operations

void CancelThreads();

. . .

// Data

public:

BOOL m_nDone;

  1. Модифицировать в классе CMainFrame функцию PreCreateWindow следующим образом:

int nSizeX = (int)(GetSystemMetrics(SM_CXMAXIMIZED) * 0.3);

int nSizeY = (int)(GetSystemMetrics(SM_CYMAXIMIZED) * 0.4);

//размеры окна

cs.cy = nSizeY;

cs.cx = nSizeX;

cs.y = 0;

cs.x = 0;

//стиль окна.

cs.style = (WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME);

cs.style &= ~ WS_MAXIMIZEBOX;

  1. С помощью мастера ClassWizard включить обработчик для сообщения OnClose() в класс CMainFrame:

void::OnClose()

{

// TODO: Add your message handler code here and/or call default

//Потоки следует завершить до закрытия главного окна приложения

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

//существовал на момент закрытия потоков.

//Если потоки завершить в функции ExitInstance объекта приложения

//возможно нарушение процесса рисования,

//так как дескриптор контекста в этом случае будет неверным.

((CMultirectApp *)AfxGetApp())->CancelThreads();

CFrameWnd::OnClose();

}

6. С помощью ClassWizard включить обработчик сообщения OnPaint в класс CMainFrame:

void CMainFrame::OnPaint()

{

CPaintDC dc(this); // device context for painting

// TODO: Add your message handler code here

CRect rect;

GetClientRect(&rect);

CBrush brush(::GetSysColor(COLOR_WINDOW));

dc.FillRect(&rect, &brush);

// Do not call CFrameWnd::OnPaint() for painting messages

}

Задание №2. Cоздать новый класс CThread приложения MultiRect, выполнив следующие действия:

  1. В меню Visual C++ Insert\New Class определить новый класс:

Name – CThread,

Base Class -.CWinThread.

Имена заголовочного и исполняемого файлов: Thread.h и Thread.cpp

  1. Модифицировать аттрибуты класса CThread в файле Thread.h следующим образом:

//Для использования класса CCriticalSection (необходимо для Visual C++ 5.0)

#include <afxmt.h>

class CThread : public CWinThread

{

//DECLARE_DYNCREATE(CThread)

//CThread(); // protected constructor used by dynamic creation

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

public:

DECLARE_DYNAMIC(CThread)

CThread(CWnd* pWnd, HDC hDC, COLORREF &cr);

// Attributes

//добавить описание критической секции m_csGDILock

public:

static CCriticalSection m_csGDILock;

//добавить описание графических объектов, необходимых для рисования

//прямоугольников

private:

CRect m_Rect;

HDC m_hDC;

CPoint m_pt, m_offset;

CBrush m_Brush;

. . .

}

  1. В файле CThread.cpp определить реализацию конструктора, деструктора, функций InitInstance и ExitInstance:

#include "stdafx.h"

#include "multirect.h"

#include "Thread.h"

#include "stdlib.h"

. . .

//

CCriticalSection CThread::m_csGDILock;

//IMPLEMENT_DYNCREATE(CThread, CWinThread)

//CThread::CThread()

//заменить предыдущие две строки, сгенерированные ClassWizard на следующие:

IMPLEMENT_DYNAMIC(CThread, CWinThread)

CThread::CThread(CWnd* pWnd, HDC hDC, COLORREF &cr)

{

m_pMainWnd = pWnd;

m_pMainWnd->GetClientRect(&m_Rect);

m_Rect.DeflateRect( 10, 10);

m_hDC = hDC;

m_pt.x = m_Rect.Width() / 2;

m_pt.y = m_Rect.Height() / 2;

m_offset.x = 10;

m_offset.y = 10;

m_Brush.CreateSolidBrush(cr);

}

CThread::~CThread()

{

}

BOOL CThread::InitInstance()

{

// TODO: perform and per-thread initialization here

return TRUE;

}

int CThread::ExitInstance()

{

// TODO: perform any per-thread cleanup here

return CWinThread::ExitInstance();

}

  1. Используя ClassWizard включить в класс CThread обработчик основной функции потока Run.

5. В файле CThread.cpp определить реализацию функции Run:

// CThread message handlers

int CThread::Run()

{

CDC dc;

dc.Attach(m_hDC);

CBrush brush(::GetSysColor(COLOR_WINDOW));

//.Инициализация генератора случайных чисел.

// Это нужно делать для каждого потока, причем тогда,

// когда он выполняется

srand((unsigned)this);

// работаем пока не измениться m_nDone

while (!((CMultirectApp *)AfxGetApp())->m_nDone)

{

CPoint pt = m_pt;

// новое положение прямоугольника

for (; ;)

{

pt += m_offset;

if (m_Rect.PtInRect(pt) && (rand() % 1000) < 900)

{

if (m_offset.x < 0)

m_offset.x -= 1;

else if (m_offset.x > 0)

m_offset.x += 1;

if (m_offset.y < 0)

m_offset.y -= 1;

else if (m_offset.y > 0)

m_offset.y += 1;

break;

}

pt = m_pt;

CPoint offset = m_offset;

while (offset == m_offset ||

offset.x == 0 && offset.y == 0 ||

offset.x == -m_offset.x && offset.y == -m_offset.y)

{

offset.x = ((rand() % 11) - 5);

offset.y = ((rand() % 11) - 5);

}

m_offset = offset;

}

// блокировка доступа к контексту устройства.

// Если доступа нет, тогда ожидание его, а затем блокируем доступ

// для других потоков

CThread::m_csGDILock.Lock();

{

CRect rect(m_pt.x-4, m_pt.y-4, m_pt.x+5, m_pt.y+5);

dc.FillRect(&rect, &brush);

m_pt = pt;

CBrush *pBrush = dc.SelectObject(&m_Brush);

dc.Rectangle(m_pt.x-4, m_pt.y-4, m_pt.x+5, m_pt.y+5);

dc.SelectObject(pBrush);

// Windows группирует вызовы функций рисования для каждого

//из потоков. Так как контекст устройства общий для всех потоков,

// перед разрешением доступа к контексту другим потокам

// необходимо выполнить все, что накопилось для данного потока

GdiFlush();

}

CThread::m_csGDILock.Unlock();

// задержка данного потока - пусть другие потоки немного поработают

Sleep(50);

}

// отсоединить дескриптор от объекта класса

dc.Detach();

return 0;

}

Задание №3. Создать 10 потоков. Для работы приложения с потоками выполнить следующие действия:

  1. Добавить в файл Multirect.cpp следующие описания:

#include "Thread.h"

// количество создаваемых потоков

#define THREAD_NUMBER 10

//цвета прямоугольников

COLORREF color[THREAD_NUMBER] = {

RGB(0, 0, 0 ), // Black

RGB(0, 0, 255), // Blue

RGB(0, 255, 0 ), // Green

RGB(252, 255, 0 ), // Yellow

RGB(255, 0, 0 ), // Red

RGB(255, 0, 255), // Magenta

RGB(185, 90, 0 ), // Brown

RGB(0, 255, 255), // Cyan

RGB(192, 192, 192), // Light Gray

RGB(128, 128, 128) // Dark Gray

};

// относительные приоритеты потоков

int cs_nPrior[] = {

THREAD_PRIORITY_IDLE,

THREAD_PRIORITY_LOWEST,

THREAD_PRIORITY_BELOW_NORMAL,

THREAD_PRIORITY_NORMAL,

THREAD_PRIORITY_ABOVE_NORMAL,

THREAD_PRIORITY_HIGHEST,

THREAD_PRIORITY_TIME_CRITICAL };

CThread *cs_pThread[THREAD_NUMBER];

  1. Добавить в функцию CMultirectApp::InitInstance() операторы инициализации потоков:

// The one and only window has been initialized, so show and update it.

m_pMainWnd->CenterWindow();

m_pMainWnd->ShowWindow(m_nCmdShow); //SW_SHOW

m_pMainWnd->UpdateWindow();

// инициализация переменной управления работой потоков

m_nDone = FALSE;

CDC *pDC = m_pMainWnd->GetDC();

// количество относительных приоритетов

int nPrior = sizeof(cs_nPrior) / sizeof(int);

for (int i = 0; i < THREAD_NUMBER; i++)

{

COLORREF cr = color[i];

// создание нового объекта потока, передавая указатель

// на объект окна, дескриптор контекста устройства и цвет

cs_pThread[i] = new CThread(m_pMainWnd, pDC->GetSafeHdc(), cr);

if (cs_pThread[i] == NULL)

break;

ASSERT_VALID(cs_pThread[i]);

// отмена автоудаления объекта потока

cs_pThread[i]->m_bAutoDelete = FALSE;

// создание потока. Поток первоначально задерживается для

// назначения ему относительного приоритета, а затем начинает

// работать

if (!cs_pThread[i]->CreateThread(CREATE_SUSPENDED))

{

delete cs_pThread[i];

cs_pThread[i] = NULL;

break;

}

VERIFY(cs_pThread[i]->SetThreadPriority(

cs_nPrior[(i * nPrior) / THREAD_NUMBER]));

// возобновление потока

cs_pThread[i]->ResumeThread();

}

  1. Добавить в файл Multirect.cpp реализацию функции CMultirectApp::CancelThreads():

// CMultirectApp commands

void CMultirectApp::CancelThreads()

{

// Возобновляние всех задержанных потоков

OnResume();

// Завершение работы всех потоков

m_nDone = TRUE;

// Чтобы исключить преждевременное завершение главного

// потока, ожидание завершения работы всех потоков

for (int i = 0; i < THREAD_NUMBER; i++)

{

if (cs_pThread[i] != NULL)

{

WaitForSingleObject(cs_pThread[i]->m_hThread, INFINITE);

delete cs_pThread[i];

}

}

}

  1. Используя ClassWizard назначить обработчики OnStop, OnResume для пунктов меню Stop и Resume

void CMultirectApp::OnStop()

{

// блокировка всех потоков запрещением доступа к контексту

// устройства. Очень удобно, так как все потоки рисуют в одном

// контексте.

CThread::m_csGDILock.Lock();

}

void CMultirectApp::OnResume()

{

// возобновление работы потоков

CThread::m_csGDILock.Unlock();

}

  1. Собрать проект и проверить работу приложения.

Контрольные вопросы:

  1. Что такое поток?

  2. Объяснить понятия «многопоточность» и «вытесняющая многозадачность» в среде Windows.

  3. Какие виды потоков поддерживает библиотека MFC? В чем их различие?

  4. Объяснить назначение класса CWinThread и кратко описать атрибуты и методы?

  5. Какие объекты синхронизации используются в Windows?

  6. Какие классы для объектов синхронизации предоставляет библиотека MFC?

  7. В каком случае необходимо перегружать функцию InitInstance в создаваемом классе потока?

  8. Какой макрос следует использовать в своем классе потока вместо генерируемого ClassWizard DECLARE_DYNCREATE?

  9. Как следует изменить приложение MultiRect для использования вместо критической секции объекта синхронизации «Событие»?

  10. Как следует изменить приложение MultiRect для использования вместо критической секции объекта синхронизации «Семафор»?