    Потоковая многозадачность 

    Потоковая многозадачность — это одно из наиболее важных улучшений в 
Windows, послужившее причиной существенного повышения производительности 
системы. Как вы, наверное, помните, в главе 1 уже говорилось о том, что в 
современных версиях Windows поддерживается два типа многозадачности. Первый 
тип основан на процессах. Такая многозадачность поддерживалось уже с первых 
версий Windows. Процесс— это, собственно говоря, программа, которая 
выполняется. В многозадачности такого типа две или более программы могут 
выполняться одновременно. Второй тип многозадачности основан на потоках. Такая 
многозадачность поддерживается оболочкой Win32 и используется в Windows 95 и 
Windows NT. Поток — это часть выполняющегося процесса. В Windows 95/NT каждый 
процесс имеет по крайней мере один поток, но потоков может быть и два, и 
больше. В потоковой многозадачности несколько частей одной и той же программы 
могут выполняться одновременно. Это дает возможность писать чрезвычайно 
эффективные программы путем разделения их на отдельные исполняемые блоки и 
управления ходом выполнения всей программы в целом. Для многозадачности такого 
типа в MFC предусмотрены специальные средства поддержки. 

    363 

    С введением потоковой многозадачности возникла необходимость в специальном 
механизме, называемом синхронизацией. Синхронизация позволяет контролировать 
выполнение потоков (и процессов) строго определенным образом. В Win32 для 
синхронизации выделена целая подсистема. Как и следовало ожидать, в MFC 
полностью поддерживаются средства многозадачности. 

    Примечание: В Windows 3.1 поддерживается только многозадачность первого 
типа (основанная на процессах) и не поддерживается потоковая многозадачность. 
Поэтому материал, изложенный в данной главе, не применим к Windows 3.1. 


    Понятие потока 

    Потоковая многозадачность становится совершенно новым направлением в 
программировании, поскольку дает программисту возможность полностью 
контролировать выполнение отдельных частей программы. Это позволяет создавать 
чрезвычайно эффективные программы. Например, можно связать один поток 
программы с выполнением работы по сортировке файла, другой поток — с 
собиранием информации об удаленном ресурсе, а третий поток — с осуществлением 
диалога с пользователем. Благодаря потоковой многозадачности каждый из этих 
потоков выполнялся бы одновременно, а ресурсы центрального процессора не 
затрачивались бы впустую. 
    Важно понимать, что все процессы имеют по крайней мере один поток 
выполнения. В дальнейшем мы будем называть его главным потоком. Но в пределах 
одного и того же процесса можно создавать несколько потоков. В общем случае, 
когда новый поток создается, он сразу же начинает выполняться. Таким образом, 
каждый процесс начинается с одного потока, к которому впоследствии могут 
добавляться дополнительные потоки. Когда они создаются, родительский процесс 
начинает выполняться не последовательно, а параллельно. Так реализуется 
потоковая многозадачность. 
    В MFC определены два типа потоков: интерфейсные и рабочие. Интерфейсный 
поток способен принимать и обрабатывать сообщения. Говоря языком MFC, 
интерфейсные потоки содержат канал сообщений. Легко догадаться, что главный 
поток MFC-программы (начинающийся при объявлении объекта типа CWinApp) 
является интерфейсным потоком. Рабочие потоки не принимают и не обрабатывают 
сообщения. Они обеспечивают дополнительные пути выполнения задачи внутри 
интерфейсного потока. При создании многопотоковых программ наиболее часто 
используются именно рабочие потоки. Дело в том, что необходимость в нескольких 
каналах сообщений возникает достаточно редко, однако во многих приложениях 
используются вспомогательные потоки, позволяющие вести фоновую обработку 
данных. Поэтому в данной главе будут рассмотрены только рабочие потоки. 

    Примечание: Важно понимать, что на уровне API и рабочие, и интерфейсные 
потоки обрабатываются одинаково. Различие между ними существует только в 
отношении иерархии классов MFC. 


    364                                     MFC: основы программирования 

    В MFC потоковая многозадачность реализуется с помощью класса CWinThread, 
Если помните, производным от него является класс CWinApp, формирующий главный 
поток приложения. 
    При использовании классов, отвечающих за работу в многозадачном режиме, в 
программу необходимо включать стандартный библиотечный файл AFXMT.H. 

    Создание рабочего потока 

    Для создания рабочего потока предназначена функция AfxBeginThread(). Ее 
прототип таков: 

    CMinThread::AfxBeginThread(AFX_THREADPROC ThreadFunc, 
    LPVOID Param, 
    int InitPriority= 
    THREAD_PRIORITY_NORMAL, 
    DINT StackSize^O, DWORD dwFlags =0, 
    LPSECURITY_ATTRIBUTES Security=NULL) ; 

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

    UINT ThreadFunc(LPVOID TFParam); 

    Значение параметра Рагат функции AfxBeginThread() передается потоковой 
функции в качестве параметра TFParam. Это 32-разрядное число может 
использоваться для любых целей. 
    Начальный приоритет потока указывается в параметре InitPriority. Если этот 
параметр равен нулю, то используются установки приоритета текущего потока. В 
противном случае данный параметр должен принимать одно из следующих значений: 

    THREAD_PRIORITY_TIME_CRITICAL 
    THREAD_PRIORITY__HIGHEST 
    THREAD_PRIORITY_ABOVE_NORMAL 
    THREAD_PRIORITY_NORMAL 
    THREAD_PRIORITY_BELOW_NORMAL 
    THREAD_PRIORITY_LOWEST 
    THREAD_PRIORITY_IDLE 

    Каждый поток имеет собственный стек. Размер стека в байтах указывается в 
параметре StackSiw. Если этот параметр равен нулю, то создаваемому потоку 
будет выделен стек такого же размера, что и у родительского потока, а, при 
необходимости, размер стека может быть увеличен. (Задание нулевого значения 
для данного параметра является общепринятым подходом.) 
    Параметр ^'/•'/доопределяет состояние выполнения потока. Если данный 
параметр равен нулю, то поток начинает выполняться немедленно. Если значение 
параметра равно CREATE_SUSPEND, то поток создастся временно остановленным, 
т.е. ожидающим запуска. (Чтобы запустить такой поток, нужно вызвать функцию CWinTliread::ResumeTlireadO.) 

    14. Потоковая многозадачность                                       365 

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

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

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

    void AfxEndThread(UINT Status); 

    Здесь параметр Status содержит статус завершения потока. Как правило, 
лучше давать потоку нормально завершиться одновременно с его потоковой функцией. 

    Пример многопотоковой программы 

    В следующей программе демонстрируются принципы многопотокового програм- i 
мирования. Каждый раз при выборе команды Execute Thread из меню Threads в : 
программе создается новый рабочий поток. Этот поток подаст 10 коротких 
звуковых сигналов и отображает на экране номер каждого сигнала вместе со своим 
идентификатором. Новый поток может быть запущен до завершения предыдущего. Для 
обновления содержимого главного окна в программе используется технология 
виртуального окна, описанная в главе 8. Пример работы программы показан на 
рис. 14.1. ' 

    Рис. 14.1. Пример работы многопотоковой программы 


    366                                          MFC: основы программирования 

    В программе используется следующий файл ресурсов: 

    tfinclude <afxres.h> 
    Unclude "ids.h" 
    
    ThreadMenu MENU 
    { 
    POPUP "&Threads" { 
    MENUITEM "Execute &Thread\tF2", IDM_THREAD 
    MENUITEM "E&xit\tF3", IDM_EXIT 
    ) 
    MENUITEM "&Help", IDM_HELP 
    ) 
    ThreadMenu ACCELERATORS 
    ( 
    VK_F1, IDM_HELP, VIRTKEY 
    VK_F2, IDM_THREAD, VIRTKEY 
    VK_F3, IDMJ3XIT, VIRTKEY 
    } 
    Это текст библиотечного файла IDS.H: 
    // THREAD.H 
    ^define IDM_THREAD 100 
    ^define IDM_HELP    101 
    #define IDM_EXIT    102 
    Вот текст файла THREAD.H, содержащего объявления порождаемых классов: 
    // THREAD.H 
    
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    ( 
    public: 
    int m X, m Y;     // текущая позиция для вывода 
    
    CDC m memDC;      // контекст устройства, связанный с 
    // виртуальным окном 
    CBitmap m_bmp;    // образ виртуального окна 
    CBrush m_bkbrush; // кисть для виртуального окна 
    CMainWin() ; 
    afx msq void OnPaintO; 
    afx_msg void OnLButtonDown(UINT Flags, CPoint Loc) ; 
    afx msg void OnThreadO; 
    afx msg void OnExitO; 
    afx msg void OnHelpO; 


    14. Потоковая многозадачность                                       367 


    DECLARE_MESSAGE_MAP() }; 
    
    // Это класс для создания приложения 
    class CApp : public CMinApp 
    { 
    public: 
    BOOL Initlnstance (); 
    ); 

    Ниже приведен текст самой программы: 


    // Использование механизма потоковой многозадачности 
    
    #include <afxwin.h> 
    ^include <string.h> 
    ^include "ids.h" 
    ^include "thread.h" 
    UINT MyThread(LPVOID HinObjPtr); 
    int ThreadID = 0; 
    int maxX, maxY; 
    // Создание главного окна приложения 
    CMainWin::CMainWin() 
    { 
    Create(NULL, "Multithreading", 
    WS_OVERLAPPEDWINDOM, rectDefault, 
    NULL, "ThreadMenu"); 
    
    // Загрузка таблицы быстрого доступа 
    if(iLoadAccelTable("ThreadMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 
    m_X = m_Y = 0; 
    maxX = GetSystemMetrics(SM_CXSCREEN) ; 
    maxY = GetSystemMetrics(SM_CYSCREEN) ; 
    CClientDC DC(this); 
    
    // создание образа виртуального окна 
    m_memDC.CreateCompatibleDC(&DC) ; 
    m bmp.CreateCompatibleBitmap(&DC, maxX, maxY) ; 
    m memDC.SelectObject(&m bmp); 
    
    // выбор стандартного фона 
    m_bkbrush.CreateStockObject(MHITE_BRUSH) ; 
    m_memDC.SelectObject(&m_bkbrush) ; 
    
    // закраска фона виртуального окна 
    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY) ; 
    ) 


    368                                     MFC: основы программирования 


    // Инициализация приложения 
    BOOL CApp::Initlnstance() 
    ( 
    m pMainWnd = new CMainWin; 
    m pMainWnd->ShowWindow(m nCmdShow); 
    m pMainWnd->UpdateWindow() ; 
    return TRUE; 
    ) 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_PAINT() 
    ON_WM_LBUTTONDOWN() 
    ON_COMMAND(IDMJTHREAD, OnThread) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    // Вывод на экран содержимого виртуального окна 
    afx msg void CMainWin::OnPaint() 
    ( 
    CPaintDC DC(this) ; 
    DC.BitBltfO, 0, maxX, maxY, &m_memDC, О, О, SRCCOPY); 
    ) 
    // Изменение координат позиции для вывода 
    // при щелчке левой кнопки мыши 
    afx_msq void CMainWin::OnLButtonDown(UINT Flags, CPoint Loc) 
    { 
    m X = Loc.x; 
    m Y = Loc.y; 
    } 
    
    // Обработка сообщения IDM_EXIT 
    afx msg void CMainWin::OnExit() f 
    int response; 
    response = MessageBox("Quit the Program?", "Exit", MB_YESNO) ; 
    if (response == IDYES) 
    SendMessage(WM CLOSE); // завершение приложения 
    ) 
    
    // Обработка сообщения IDM HELP 
    afx msg void CMainWin::OnHelp() 1 
    MessageBox("Multithreading", "Help"); 
    ) 
    
    // Обработка сообщения IDM^THREAD 
    afx msg void CMainWin::OnThread() 


    14. Потоковая многозадачность                                       369 


    AfxBeg:i-nThread(MyThread, this);                                    f 
    
    // Рабочий поток                                                    • 
    UINT MyThread(LPVOID WinObjPtr)                                     | 
    { 
    int i; 
    TEXTMETRIC tm; 
    int Tid = ThreadID; 
    char str[255] ; 
    ThreadID++; 
    CMainMin *ptr = (CMainWin *) WinObjPtr; 
    
    // получение метрик текста 
    ptr->m_memDC.GetTextMetrics(&tm) ; 
    
    for(i=0; i<10; i++) ( 
    Sleep(500) ; 
    
    wsprintf (str, "Thread #%d, beep #%d", Tid, i);                . 
    ptr->m_memDC.TextOut(ptr->m_X, ptr->m_Y, str, strlen (str)); 
    ptr->m Y = ptr->m Y + tm.tmHeight + tm.tmExternalLeading; 
    ptr->InvalidateRect(NULL) ; 
    MessageBeep(MB_OK) ; 
    ) 
    ThreadID--; 
    return 0; 
    } 
    CApp App; // создание экземпляра приложения 

    В этой программе указатель на объект главного окна передается в качестве 
параметра функции MyThreadQ. Данный параметр используется для получения 
доступа к членам класса CMainWin, хотя, в принципе, он может использоваться 
совершенно произвольно. 
    В глобальной переменной ThreadID содержится номер копии функции MyThreadQ, 
которая выполняется в данный момент. Значение переменной увеличивается при 
входе в функцию и уменьшается при ее завершении. Данное значение отображается 
на экране просто для примера. Другого значения эта переменная не имеет. 
    Обратите внимание, что в цикле внутри функции MyThreadQ вызывается функция 
Sleep(). Эта API-функция приостанавливает выполнение вызвавшего ее потока на 
заданное число миллисекунд. Благодаря ей выполнение функции MyThread() ; 
несколько замедляется, чтобы удобнее было наблюдать эффект "многопоточности" 
программы. На каждом шаге цикла с помощью другой API-функции, MessageBeepQ, 
подается короткий звуковой сигнал. 
    Поскольку функция MyThreadO во время своего выполнения является 
независимым потоком, остальная часть программы остается активной. Попробуйте, 
например, провести следующий эксперимент: выберите в меню Threads команду 
Execute Thread, а затем, до завершения выполнения потока, выберите снова ту же 
команду. Вы | 

    370                                    MFC: основы программирования 

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

    Использование нескольких потоков 

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

    // Работа с двумя потоками 
    #include <afxwin.h> 
    #include <string.h> 
    #include "ids.h" 
    ^include "thread.h" 
    UINT MyThreadI(LPVOID WinObjPtr); 
    UINT MyThread2(LPVOID WinObjPtr); 
    int FirstThreadID = 0; 
    int SecondThreadID = 0; 
    jnt maxX, maxY; 
    // Создание главного окна приложения 
    CMainWin::CMainWin() 
    ( 
    Create(NULL, "Multithreading", 
    WS_OVERLAPPEDWINDOW, rectDefault, 
    NULL, "ThreadMenu"); 
    
    // Загрузка таблицы быстрого доступа 
    if('LoadAccelTable("ThreadMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 
    m_X = m_Y- = 0; 
    maxX = GetSystemMetrics(SM_CXSCREEN); 
    maxY = GetSystemMetrics(SM_CYSCREEN) ; 
    CCiientDC DC(this) ; 
    
    // создание образа виртуального окна 
    m_memDC.CreateCompatibleDC(&DC) ; 
    m bmp.CreateCompatibleBitmap(&DC, maxX, maxY); 
    m memDC.SelectObject(&m_bmp); 
    
    // выбор стандартного фона 
    m_bkbrush.CreateStockObj ect(WHITE_BRUSH) ; 


    14. Потоковая многозадачность                                       371 


    » 
    m_memDC.SelectObject(&m_bkbrush) ; 
    
    // закраска фона виртуального окна 
    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY); 
    } 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance () 
    ( 
    iT]_pMainMnd = new CMainWin; 
    m_pMainWnd->ShowMindow(m nCmdShow) ; 
    rn__pMainHnd->UpdateWindow() ; 
    return TRUE; 
    ) 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainMin, CFrameWnd) 
    ON_WM_PAINT () 
    ON_WM_LBUTTONDOWN() 
    ON_COMMAND(IDM_THREAD, OnThread) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    // Вывод на экран содержимого виртуального окна 
    afx_msq void CMainWin::OnPaint() 
    ( 
    CPaintDC DC (this) ; 
    DC.BitBlt(0, 0, maxX, maxY, Sm_memDC, О, О, SRCCOPY) ; 
    1 
    // Изменение координат позиции для вывода 
    // при щелчке левой кнопки мыши 
    afx_msq void CMainWin::OnLButtonDown(UINT Flags, CPoint Loc)         ! 
    1 
    m X = Loc.x; 
    m Y = Loc.y; 
    } 
    
    // Обработка сообщения IDM_EXIT 
    afx msg void CMainWin::OnExit() 
    ( 
    int response; 
    response = MessageBox("Quit the Program?", "Exit", MB_YESNO) ; 
    if(response == IDYES) 
    SendMessage(HM CLOSE); // завершение приложения 
    } 
    // Обработка сообщения IDM HELP 


    372                                     MFC: основы программирования 


    afx_msg void CMainWin::OnHelp() 
    ( 
    MessageBox("Multithreading", "Help") ; 
    1 
    
    // Обработка сообщения IDM THREAD 
    afx msg void CMainWin::OnThread() 
    ( 
    // запуск двух рабочих потоков 
    AfxBeginThread(MyThreadI, this) ; 
    AfxBeginThread(MyThread2, this) ; 
    ) 
    // Первый рабочий поток 
    HINT MyThreadI(LPVOID WinObjPtr) 
    f 
    int i; 
    TEXTMETRIC tm; 
    char str[255] ; 
    int Tid = FirstThreadID; 
    FirstThreadID++; 
    CMainWin *ptr = (CMainWin *) WinObjPtr; 
    
    // получение метрик текста 
    ptr->m_memDC.GetTextMetrics(Stm) ; 
    
    for(i=0; i<10; i++) { 
    Sleep (500) ; 
    wsprintf (str, "First Thread #°od, beep #%d", Tid, i); 
    ptr->m_memDC.TextOut(ptr->m_X, ptr->m_Y, str, strlen (str)) ; 
    ptr->m_Y = ptr->m_Y + tm.tniHeight + tm.tmExternalLeading; 
    ptr->InvalidateRect(NULL) ; 
    MessageBeep(MB_OK) ; 
    } 
    FirstThreadID--; 
    return 0; 
    
    // Второй рабочий поток 
    HINT MyThread2(LPVOID WinObjPtr) 
    int i; 
    TEXTMETRIC tm; 
    char str[255] ; 
    int Tid = SecondThreadID; 
    SecondThreadID++; 
    CMainWin *ptr = (CMainWin *) WinObjPtr; 


    14. Потоковая многозадачность                                       373 


    // получение метрик текста 
    ptr->m_memDC.GetTextMetrics(&tm) ; 
    
    for(i=0; i<10; i++) { 
    Sleep (200) ; 
    wsprintf (str, "Second Thread #%d, beep #%d", Tid, i); 
    ptr->m_memDC.TextOut(ptr->m X, ptr->m Y, str, strlen (str)) ; 
    ptr->m_Y = ptr->m_Y + tm.tmHeight + tm.tmExternalLeading; 
    ptr->InvalidateRect(NULL) ; 
    MessageBeep(MB_OK) ; 
    } 
    SecondThreadID--; 
    return 0; 
    } 
    CApp App; // создание экземпляра приложения 


    Рис. 14.2. Пример работы второй многопотоковой программы 

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

    374                                    MFC: основы программирования 


    Остановка и возобновление выполнения потока 

    Остановить выполнение потока можно с помощью функции SuspendThread(). В 
остановленном состоянии поток не выполняется. Продолжить выполнение потока 
можно с помощью функции ResumeThread(). Прототипы этих функций таковы: 

    DWORD CWinThread::SuspendThread() ; 
    DWORD CWinThread::ResumeThread() ; 

    Каждый поток имеет связанный с ним счетчик остановок. Если этот счетчик 
равен нулю, значит поток выполняется нормально. При ненулевом значении 
счетчика поток находится в остановленном состоянии. С каждым вызовом функции 
SuspendThread() значение счетчика остановок увеличивается на единицу. И, 
наоборот, с каждым вызовом функции ResunieThread() значение счетчика остановок 
уменьшается на единицу. Остановленный поток может продолжить выполнение только 
после того, как значение счетчика достигнет нуля. Поэтому предполагается, что 
число вызовов функции ResumeThread() должно равняться числу вызовов функции SuspendThread(). 
    Обе функции возвращают предыдущее значение счетчика остановок данного 
потока или -1, если произошла ошибка. 

    Управление приоритетами потоков 

    С каждым потоком связана определенная установка приоритета. Эта установка 
представляет собой комбинацию двух значений: значения общего класса приоритета 
процесса и значения приоритета самого потока относительно данного класса. 
Таким образом, фактический приоритет потока определяется путем сложения класса 
приоритета процесса и уровня приоритета самого потока. Приоритет потока 
показывает, сколько времени работы процессора требуется потоку. Для потоков с 
низким приоритетом требуется мало времени, а для потоков с высоким приоритетом 
— много времени. Конечно, количество времени, которое занимает поток у 
процессора, существенным образом влияет на характеристики выполнения потока и 
его взаимодействие с другими, выполняющимися в данный момент потоками. 
    Получить класс приоритета процесса можно с помощью функции 
GetPriorityClassQ, а установить класс приоритета можно с помощью функции 
SetPriorityClass(). Обе они являются API-функциями и не входят в класс 
CWinThread. Прототипы этих функций таковы: 

    DWORD GetPriorityClass(HANDLE hApp); 
    BOOL SetPriorityClass(HANDLE hApp, DWORD dwPrlority); 

    Здесь параметр hApp является дескриптором процесса. Функция 
GetPriorityClass() возвращает значение класса приоритета приложения. В функции 
SetPriorityClass() параметр civ/Priority задает новый класс приоритета. Ниже 
показаны константы, соответствующие классам приоритетов в порядке убывания: 

    REALTIME_PRIORITY_CLASS 
    HIGH_PRIORITY_CLASS 
    NORMAL_PRIORITY_CLASS 
    IDLE_PRIORITY_CLASS 


    14. Потоковая многозадачность                                       375 

    По умолчанию программе присваивается приоритет NORMAL_PRIORITY_CLASS. Как 
правило, нет причин его менять. Фактически, изменение класса приоритета 
процесса может негативно сказаться на производительности всей системы. 
Например, при увеличении класса приоритета программы до 
REALTIME_PRIORITY_CLASS она захватит все ресурсы процессора. В некоторых 
приложениях может возникнуть необходимость повысить значение приоритета 
приложения, но обычно этого не следует делать. В примерах данной главы 
используется стандартный уровень приоритета, присваиваемый программе по умолчанию. 
    Приоритет потока (независимо от класса приоритета) говорит о том, сколько 
времени процессора занимает отдельный поток в пределах процесса. При создании 
потока ему присваивается нормальный приоритет. Но это значение можно изменить 
— даже во время выполнения потока. 
    Приоритеты потоков контролируются функциями-членами класса CWinThread. 
Определить значение приоритета потока можно с помощью функции 
GetThreadPriorityO Увеличить или уменьшить приоритет потока можно с помощью 
функции SetThreadPriority(). Прототипы этих функций таковы: 

    BOOL CHinThread::SetThreadPriority(int Priority); 
    int CWinThread::GetThreadPriority () ; 

    В функции SetThreadPriorityO параметр Priority определяет новую установку 
приоритета. В функции GetThreadPriorityO возвращается текущая установка. Ниже 
приведены константы, соответствующие установкам приоритетов в порядке убывания: 

    THREAD_PRIORITY_TIME_CRITICAL 
    THREAD_PRIORITY_HIGHEST 
    THREAD_PRIORITY_ABOVE_NORMAL 
    THREAD_PRIORITY_NORMAL 
    THREAD_PRIORITY_BELOH_NORMAL 
    THREAD_PR IORITY_LOWEST 
    THREAD_PRIORITY_IDLE 

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

    Синхронизация 

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

    376                                     MFC: основы программирования 

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

    Понимание проблемы синхронизации 

    В Windows имеются специальные ссрвисы, которые позволяют определенным 
образом ограничить доступ к разделяемым ресурсам, ведь без помощи операционной 
системы отдельный процесс или поток не может сам определить, имеет ли он 
единоличный доступ к ресурсу. Чтобы понять это, представьте, что вы пишите 
программу для многозадачной операционной системы, не имеющей средств поддержки 
синхронизации. Представьте также, что вы хотите создать два одновременно 
выполняющихся процесса, А и В, которые периодически требуют доступ к 
некоторому ресурсу R (например файлу на диске). Однако в определенный момент 
времени этот ресурс может быть использован только одной задачей. Чтобы 
запретить одной из программ обращаться к ресурсу R в тот момент, когда с ним 
работает другая программа, может быть предложено следующее решение. Во-первых, 
необходимо создать переменную nag, к которой имеют доступ обе программы. 
Начальное значение переменной равно нулю. Во-вторых, прежде чем перейти к 
блоку программы, в котором происходит обращение к ресурсу R, нужно подождать 
обнуления 4)лага (т.е. признака того, что ресурс свободен), затем установить 
флаг (присвоить переменной flag ненулевое значение), обратиться к ресурсу R и 
после этого снова сбросить флаг в ноль. Таким образом, для доступа к ресурсу R 
в программе должен выполняться следующий фрагмент кода: 

    while(flag); // ожидает, пока флаг будет сброшен 
    :lag = 1; // установка флага 
    / ... доступ к ресурсу R ... 
    flag = 0; // сброс флага 

    Смысл данного кода заключается в том, чтобы ни один процесс не мог 
обратиться к ресурсу R до тех пор, пока переменная flag не станет равной нулю. 
Теоретически, такой подход близок к правильному решению проблемы. Но 
оказывается, что на практике он далеко не всегда срабатывает! Давайте 
рассмотрим, почему так происходит. 
    Дело в том, что при использовании данного кода возможно одновременное 
обращение обоих процессов к ресурсу R. По сути, в цикле while выполняется 

    14. Потоковая многозадачность                                       377 

    периодическое сравнение значения переменной flag с нулем. Проще говоря, 
осуще-Д^ ствляется проверка флага. Если флаг сброшен, то в следующей строке 
кода флаг туту же устанавливается. Проблема заключается в том, что обе 
операции могут выпол- | няться в разные промежутки времени. Между этими 
промежутками значение пере- j менной flag может быть изменено другим 
процессом, что приведет к одновременному обращению обоих процессов к ресурсу 
R. Представьте, что процесс А при выполнении цикла while обнаруживает, что 
переменная flag равна нулю, и собирается обратиться к ресурсу R. Но еще до 
того, как значение переменной flag будет установлено в единицу, заканчивается 
время, выделенное данному процессу, и начинает выполняться процесс В. Он 
выполняет свой цикл while, и также находит, что переменная flag не 
установлена, следовательно ресурс R свободен. Но в следующий момент времени с 
данным ресурсом начнет работать и процесс А! Суть проблемы состоит в том, что 
проверка и изменение значения переменной flag не являются одной непрерывной 
операцией. Как было показано, эти действия могут выполняться в разные 
промежутки времени. И что бы вы не предпринимали, на уровне приложения 
невозможно гарантировать обращение к ресурсу R одного и только одного процесса 
в любой момент времени. 
    Решение вышеизложенной проблемы синхронизации изящно и, в то же время, 
просто. Операционная система (в данном случае Windows) содержит процедуру, 
которая в течение одной непрерывной операции проверяет и, если это возможно. 
устанавливает флаг доступа к ресурсу. На языке разработчиков операционной 
системы такая операция называется операцией проверки и установки. Исторически 
сложилось так, что флаги, используемые для обеспечения синхронизации и 
управления доступом к ресурсам, называются семафорами. Интерфейс Win32 API 
обеспечивает поддержку семафоров и других объектов синхронизации. Естественно, 
что библиотека MFC также включает поддержку данных объектов. 

    Объекты синхронизации 

    Интерфейс Win32 поддерживает четыре типа объектов синхронизации. Все они. 
так или иначе, основаны на понятии семафора. Первым типом объектов является 
собственно семафор, или классический (стандартный) семафор. Он позволяет 
ограниченному числу процессов или потоков обращаться к одному ресурсу. При 
этом доступ к ресурсу либо полностью ограничен (один и только один поток или 
процесс может обратиться к ресурсу в определенный момент времени), либо 
одновременный доступ получает лишь малое число процессов и потоков. Сема4юры 
реализуются с помощью счетчика, значение которого уменьшается, когда задаче 
выделяется семафор, и увеличивается, когда задача освобождает семафор. 
    Вторым типом объектов синхронизации является исключающий семафор (mutex 
semaphore). Он предназначен для полного ограничения доступа к ресурсу, чтобы в 
любой момент времени к ресурсу мог обратиться только один процесс или поток. 
Фактически, это особая разновидность классического семафора. 
    Третьим типом объектов синхронизации является событие, или объект события 
(event object). Он используется для блокирования доступа к ресурсу до тех пор, 
пока какой-нибудь другой процесс или поток не заявят о том, что данный ресурс 
может быть использован. Таким образом, данный объект сигнализирует о 
выполнении требуемого события. 

    378                                     MFC: основы программирования 

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

    Классы синхронизации в MFC 

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

    Таблица 14.1. Классы синхронизации 

    Класс                 Реализуемый объект синхронизации 

    CCriticalSection        Критический раздел 
    CEvent               Объект события 
    CMutex               Исключающий семафор 
    CSemaphore           Классический семафор 

    Данные классы порождаются от класса CSyncObject, который реализует базовый 
механизм синхронизации, общий для всех этих классов. 
    В MFC определены также два вспомогательных класса синхронизации: 
CSingleLock и CMultiLock. Они контролируют доступ к объекту синхронизации и 
содержат функции-члены, используемые для предоставления и освобождения таких 
объектов. Класс CSingleLock управляет доступом к одному объекту синхронизации, 
а класс CMultiLock — к нескольким объектам. В следующих параграфах 
рассматривается только класс CSingleLock. 

    Класс CSingleLock 

    Когда объект синхронизации создан, доступ к нему можно контролировать с 
помощью класса CSingleLock. Для этого необходимо сначала создать объект типа 
CSingleLock с помощью следующего конструктора: 

    CSingleLock (CSyncObject *SyncOb, BOOL Jn-lt.ia-;State=FALSE) ; 

    Здесь параметр SyncOb является указателем на объект синхронизации, 
например семафор. Значение параметра InilialState определяет, должен ли 
конструктор пытаться получить доступ к данному объекту. Если этот параметр не 
равен нулю, то доступ будет получен. В противном случае попыток получить 
доступ не будет. 

    14. Потоковая многозадачность                                       379 

    Когда объект типа CSingleLock создан, доступ к объекту, на который 
указывав параметр SyncOb, может контролироваться с помощью двух функций: 
Lock() i Unlock(). Их прототипы таковы: 

    BOOL CSingleLock: : Lock (DWORD dwOe-Iay=INFINITE) ; 
    BOOL CSingleLock::Unlock (); 
    BOOL CSingleLock: :Unlock (LONG Count, LONG * Previous=WL~L} ; 

    Функция Lock() предназначена для получения доступа к объекту 
синхронизации. Вызвавший ее поток останавливается до завершения данной 
функции. Значение параметра civ/Delay определяет, как долго функция будет 
ожидать получения доступа к требуемому объекту. Если заданный срок истек, 
генерируется ошибка. Если время ожидания не определено, можно указать значение 
INFINITE. Функция возвращает ненулевое значение при успешном завершении (т.е. 
когда доступ получен) и ноль, если время истекло. Каждый раз при успешном 
завершении функции Lock() значение счетчика, связанного с объектом 
синхронизации, уменьшается на единицу. 
    Функция Unlock() освобождает объект синхронизации, давая таким образом 
другим потокам возможность использовать его. В первом варианте функции 
значение счетчика, связанного с данным объектом, увеличивается на единицу. Во 
втором варианте параметр Count определяет, на сколько это значение должно быть 
увеличено. Чаще всего данный вариант функции используется с семафорами, 
которые обеспечивают множественный доступ к ресурсам. Параметр Previous 
указывает на переменную, в которую будет записано предыдущее значение 
счетчика. Если в этом нет необходимости, то можно задать NULL. В обоих 
вариантах функция возвращает ненулевое значение при успешном завершении и ноль 
— в противном случае. 
    При работе с классом CSingleLock общая процедура управления доступом к 
ресурсу такова: 
    1. Создайте объект типа CSyncObject (например семафор), который будет 
использоваться для управления доступом к ресурсу. 
    2. С помощью объекта синхронизации, созданного на первом шаге, создайте 
объект типа CSingleLock. 
    3. Для получения доступа к ресурсу вызовите функцию Lock(). 
    4. Выполните обращение к ресурсу. 
    5. Вызовите функцию UnlockQ, чтобы освободить ресурс. 
    В конце главы приведена программа, в которой с помощью семафора и объекта 
события реализуются описанные выше действия. 
    Примечание: Базовый класс синхронизации, CSyncObject, также содержит 
функции Lock() и Unlock(), которые могут использоваться для получения доступа 
и освобождения объекта синхронизации (например семафора) без обращения к 
классу CSingleLock или CMultiLock. Однако фирма Microsoft рекомендует все-таки 
работать с классами CSingleLock и CMultiLock. Поэтому в данной главе мы будет 
придерживаться этого совета. 

    380                                    MFC: основы программирования 


    Работа с семафорами 

    В этом параграфе рассматривается, как обеспечить синхронизацию потоков на 
основе семафоров. Прежде всего необходимо создать семафор путем объявления 
объекта типа CSemaphore. Конструктор данного класса имеет такой прототип: 

    CSemaphore(LONG InitialCount=\, 
    LONG MaxCount=l, 
    LPSTR lpszName=mLL, 
    LPSECURITY_ATTRIBUTES Securi?y=NULL) ; 

    Семафор позволяет одному или нескольким потокам получать доступ к объекту. 
Допустимое число потоков, которым будет разрешен одновременный доступ, 
указывается в параметре MaxCount. Если его значение равно единице, то семафор 
будет исключающим, т.е. только один поток или процесс сможет одновременно 
обращаться к ресурсу. 
    Семафоры имеют счетчик, указывающий количество задач, которым в настоящий 
момент предоставлен доступ к ресурсу. Если значение счетчика равно нулю, то 
последующий доступ к ресурсу запрещается до тех пор, пока одна из задач не 
освободит семафор. Начальное значение счетчика семафора указывается в 
параметре InitialCount. Если он равен нулю, то все потоки, ожидающие семафор, 
будут приостановлены, пока семафор не освободится где-нибудь в программе. Но, 
обычно, данное значение задается равным, как минимум, единице, чтобы хотя бы 
один поток мог получить семафор. Значение параметра InituilCounl должно быть 
неотрицательным и не превышать значения параметра MaxCount. 
    Параметр Ips^Name указывает на строку, содержащую имя объекта семафора. 
Поименованные семафоры становятся системными объектами и могут использоваться 
другими процессами. Таким образом, когда два процесса вызывают семафоры с 
одинаковыми именами, обоим процессам будет предоставлен один и тот же семафор. 
Это позволяет синхронизировать процессы. Вместо имени строки можно задавать 
NULL. В этом случае семафор будет локализован в пределах одного процесса. 
    Параметр Security является указателем на набор атрибутов прав доступа, 
связанный с семафором. Если этот параметр равен NULL, то семафор наследует 
данный набор у вызвавшего его потока. 

    Пример работы с семафором 

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

    14. Потоковая многозадачность                                       381 


    Рис. 14.3. Пример работы с семафором 

    В программе используется тот же файл ресурсов и библиотечный файл IDS.H, 
что и в предыдущей программе. Ниже приведен текст файла SEMA.H, содержащего 
объявления порождаемых классов: 

    //  SEMA.H 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameMnd 
    ( 
    public: 
    int m X, m Y; // текущая позиция для вывода 
    
    CDC m memDC; // контекст устройства, связанный с 
    // виртуальным окном 
    CBitmap m bmp; // образ виртуального окна 
    CBrush m bkbrush; // кисть для виртуального окна 
    
    CSemaphore Sema; // объект семафора 
    CMainWin() ; 
    afx msg void OnPaintO; 
    afx_msg void OnLButtonDown(UINT Flags, CPoint Loc) ; 
    afx msg void OnThreadO; 
    afx msg void OnExitO; 
    afx_msg void OnHelpO; 


    382                                     MFC: основы программирования 


    DECLARE_MESSAGE_MAP() 
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    ( 
    public: 
    BOOL Initlnstance (); 
    
    Вот текст самой программы: 
    // Использование семафора 
    
    Tinclude <afxwin.h> 
    ^include <afxmt.h> 
    ^include <string.h> 
    ^include "ids.h" 
    ttinclude "sema.h" 
    UINT MyThreadI(LPVOID WinObjPtr); 
    UINT MyThread2(LPVOID WinObjPtr); 
    int FirstThreadID = 0; 
    int SecondThreadID = 0; 
    int maxX, maxY; 
    // Создание главного окна приложения 
    CMainWin::CMainWin() 
    ( 
    Create (NULL, "Using a Semaphore", 
    WS__OVERLAPPEDWINDOW, rectDefault, 
    NULL, "ThreadMenu"); 
    
    // Загрузка таблицы быстрого доступа 
    if('LoadAccelTable("ThreadMenu")) 
    MessageBox ("Cannot Load Accelerators", "Error"); 
    m_X = m_Y = 0; 
    maxX = GetSystemMetrics(SM_CXSCREEN) ; 
    maxY = GetSystemMetrics(SM_CYSCREEN) ; 
    CClientDC DC(this); 
    
    // создание образа виртуального окна 
    m_memDC.CreateCompatibleDC(&DC) ; 
    m bmp.CreateCompatibleBitmap(&DC, maxX, maxY) ; 
    m memDC.SelectObject(&m bmp); 
    
    // выбор стандартного фона 
    m_bkbrush.CreateStockObject(WHITE_BRUSH) ; 


    74. Потоковая многозадачность                                       383 


    m memDC.SelectObject(&m bkbrush);                               • 
    I / закраска фона виртуального окна                              i 
    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY); 
    } 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance () 
    { 
    m_pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow) ; 
    m pMainWnd->UpdateWindow(); 
    return TRUE; 
    } 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_PAINT() 
    ON_WM_LBUTTONDOWN() 
    ON_COMMAND(IDM_THREAD, OnThread) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    // Вывод на экран содержимого виртуального окна 
    afx_msg void CMainWin::OnPaint() 
    { 
    CPaintDC DC(this);                                                 ,, 
    DC.BitBlt(0, 0, maxX, maxY, &m_memDC, О, О, SRCCOPY) ; 
    ) 
    // Изменение координат позиции для вывода 
    // при щелчке левой кнопки мыши 
    afx msq void CMainWin::OnLButtonDown(UINT Flags, CPoint Loc) 
    ( 
    m_X = Loc.x; 
    m Y •= Loc.y; 
    ) 
    
    // Обработка сообщения IDM_EXIT 
    afx msg void CMainWin::OnExit () 
    { 
    int response; 
    response = MessageBox ("Quit the Program?", "Exit", MB_YESNO) ; 
    if(response == IDYES) 
    SendMessage(WM CLOSE); // завершение приложения 
    ) 
    // Обработка сообщения IDM_HELP 


    384                                     MFC: основы программирования 


    afx msg void CMainWin::OnHelp () 
    ( 
    MessageBox("Semaphore", "Help"); 
    ) 
    
    // Обработка сообщения IDM THREAD 
    afx msg void CMainWin::OnThread() 1 
    AfxBeginThread(MyThreadI, this) ; 
    AfxBeginThread(MyThread2, this) ; 
    
    // Первый рабочий поток 
    UINT MyThreadI(LPVOID WinObjPtr) 
    ( 
    int i; 
    TEXTMETRIC tm; 
    char str [255] ; 
    int Tid = FirstThreadID; 
    FirstThreadID++; 
    CMainWin *ptr = (CMainWin *) WinObjPtr; 
    // получение метрик текста 
    ptr->m memDC.GetTextMetrics(&tm) ; 
    CSingleLock SyncOb(&(ptr->Sema)) ; 
    SyncOb.Lock (); // получение семафора 
    
    for(i=0; i<10; i++) ( 
    Sleep (500) ; 
    wsprintf (str, "First Thread #%d, beep #%d", Tid, i); 
    ptr->m_memDC.TextOut(ptr->m_X, ptr->m_Y, str, strlen (str)); 
    ptr->m Y = ptr->m Y + tm.tmHeight + tm.tmExternalLeading; 
    ptr->InvalidateRect(NULL) ; 
    MessageBeep(MB_OK) ; 
    t 
    FirstThreadID--; 
    
    SyncOb.Unlock(); // освобождение семафора 
    return 0; 
    // Второй рабочий поток 
    UINT MyThread2(LPVOID WinObjPtr) 
    ( 
    int i; 
    TEXTMETRIC tm; 
    char str[255] ; 
    int Tid = SecondThreadID; 


    14. Потоковая многозадачность                                       385 


    SecondThreadID++; 
    CMainWin *ptr = (CMainWin *) WinObjPtr; 
    
    // получение метрик текста 
    ptr->m_memDC.GetTextMetrics(&tm) ; 
    CSingleLock SyncOb(&(ptr->Sema)) ; 
    SyncOb.Lock(); // получение семафора 
    
    for(i=0; i<10; i++) { 
    Sleep (200) ; 
    wsprintf (str, "Second Thread #%d, beep #°od", Tid, i); 
    ptr->m_memDC.TextOut(ptr->m_X, ptr->m_Y, str, strlen (str)); 
    ptr->m_Y = ptr->m_Y + tm.tmHeight + tm.tmExternalLeading; 
    ptr->InvalidateRect(NULL) ; 
    MessageBeep(MB_OK); 
    } 
    SecondThreadID--; 
    SyncOb.Unlock(); // освобождение семафора 
    return 0; 
    } 
    САрр Арр; // создание экземпляра приложения 

    Класс CMainWin содержит объект семафора, называющийся Sema. Он использу-1 
ется внутри функций MyThreadl() и MyThread2() для разграничения доступа 
потоков к ресурсам. В каждой из функций создается объект типа CSingleLock, а 
затем вызывается функция Lock(). Когда первый поток получает доступ к 
семафору, другой поток приостанавливается до тех пор, пока первый поток не 
освободит семафор с помощью функции Unlock(). Таким образом, семафор 
предоставляется одновременно только одному потоку. Это напоминает работу с 
исключающим семафором. Попробуйте поэкспсримснтировать с предоставлением 
доступа к семафору сразу нескольким потокам. 

    Работа с объектами событий 

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

    CEvent (BOOL InitialState=FALS'E, 
    BOOL ManuaJ=FALSE, 
    LPSTR JpszName=NULL, 
    LPSECURITY_ATTRIBUTES Security=NULL) ; 

    Значение параметра InitialStare определяет начальное состояние объекта. 
Если оно равно TRUE, то объект события установлен (событие произошло), а если FALSE, 

    386                                     MFC: основы программирования 

    то объект не установлен или сброшен (событие не произошло). Параметр 
Manual указывает, каким образом состояние объекта события будет изменяться при 
выполнении события. Если значение параметра равно TRUE (не ноль), то объект 
может быть сброшен только путем вызова функции ResetEventQ. В противном случае 
объект автоматически сбрасывается после предоставления блокированному потоку 
доступа к ресурсу. 
    Параметр IpszName указывает на строку, содержащую имя объекта события. 
Поименованные объекты событий становятся системными объектами и могут 
использоваться другими процессами. Таким образом, когда два процесса вызывают 
объекты событий с одинаковыми именами, обоим процессам будет предоставлен один 
и тот же объект. Это также позволяет синхронизировать процессы. Вместо имени 
строки можно задавать NULL. В этом случае данный объект будет локализован в 
пределах одного процесса. 
    Параметр Security является указателем на набор атрибутов прав доступа, 
связанный с объектом события. Если этот параметр равен NULL, то объект 
наследует данный набор у вызвавшего его потока. 
    Когда объект события создан, то поток, ожидающий данное событие, должен с 
помощью этого объекта создать объект типа CSingleLock, а затем вызвать функцию 
CSingleLock::Lock(). При этом выполнение данного потока останавливается до тех 
пор, пока не произойдет ожидаемое событие. 
    Для сигнализации о том, что событие произошло, предназначена функция 
SetEvent(), прототип которой таков: 

    BOOL CEvent::SetEvent(); 

    При вызове данной функции первый поток, ожидающий событие, выйдет из 
остановленного состояния (функция CSingleLock: :Lock() завершится) и продолжит 
свое выполнение. Функция возвращает ненулевое значение при успешном завершении 
и ноль — в противном случае. 
    При работе с программным объектом события необходимо вызывать функцию 
ResetEventQ каждый раз, когда поступил сигнал о выполнении события. Вот 
прототип этой функции: 

    BOOL CEvent::ResetEvent(); 

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

    Пример работы с объектом события 

    Чтобы продемонстрировать вышесказанное, заменим в предыдущем примере 
семафор на объект события. В новой версии программы функция MyThreadlQ будет 
блокирована до тех пор, пока функция MyThread2() не завершится и не 
сигнализирует о своем завершении. Таким образом, завершение функции 
MyThread2() является событием, которого ожидает функция MyThreadlQ Пример 
работы программы показан на рис. 14.4. 

    74. Потоковая многозадачность                                       387 


    Рис. 14.4. Пример работы с объектом события 

    В программе используется тот же файл ресурсов и библиотечный файл IDS.H, 
что и в предыдущей программе. Ниже приведен текст файла EVENT.H, содержащего 
объявления порождаемых классов: 

    //  EVENT.H 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd 
    ( 
    public: 
    int m X, m Y; // текущая позиция для вывода 
    
    CDC m memDC; // контекст устройства, связанный с 
    // виртуальным окном 
    CBitmap m bmp; // образ виртуального окна 
    CBrush m bkbrush; // кисть для виртуального окна 
    
    CEvent Event; // объект события 
    CMainWin() ; 
    afx msg void OnPaintO; 
    afx msg void OnLButtonDown(DINT Flags, CPoint Loc) ; 
    afx msg void OnThreadO; 
    afx msg void OnExitf); 
    afx msg void OnHelpO; 


    388                                    MFC: основы программирования 


    DECLARE_MESSAGE_MAP() 
    
    // Это класс для создания приложения 
    class CApp : public CWinApp 
    ( 
    public: 
    BOOL Initlnstance (); 
    Вот текст самой программы: 
    // Использование объекта события 
    
    «include <afxwin.h> 
    ^include <afxmt.h> 
    #include <string.h> 
    #include "ids.h" 
    #include "event.h" 
    UINT MyThreadI(LPVOID WinObjPtr); 
    HINT MyThread2(LPVOID WinObjPtr); 
    int FirstThreadID = 0; 
    int SecondThreadID = 0; 
    int maxX, maxY; 
    // Создание главного окна приложения 
    CMainWin::CMainWin() 
    f 
    Create(NULL, "Using an Event Object", 
    WS_OVERLAPPEDWINDOW, rectDefault, 
    NULL, "ThreadMenu"); 
    
    // Загрузка таблицы быстрого доступа 
    if(SLoadAccelTable("ThreadMenu")) 
    MessageBox("Cannot Load Accelerators", "Error"); 
    m_X = m_Y = 0; 
    maxX = GetSystemMetrics(SM_CXSCREEN) ; 
    maxY = GetSystemMetrics(SM_CYSCREEN) ; 
    CClientDC DC (this) ; 
    
    // создание образа виртуального окна 
    m_memDC.CreateCompatibleDC(&DC) ; 
    m bmp.CreateCompatibleBitmap(&DC, maxX, maxY) ; 
    m memDC.SelectObject(&m bmp); 
    
    // выбор стандартного фона 
    m_bkbrush.CreateStockObject(WHITE_BRUSH) ; 


    14. Потоковая многозадачность                                      389 


    m memDC.SelectOoject(&m bkbrush); 
    
    // закраска фона виртуального окна 
    m_memDC.PatBIt(0, 0, maxX, maxY, PATCOPY); 
                                                  ; 
    
    // Инициализация приложения 
    BOOL CApp::Initlnstance () 
    ( 
    m pMainWnd = new CMainMin; 
    m pMainWnd->ShowWindow (m nCrndShow) ; 
    m_pMainWnd->UpdateWindow() ; 
    return TRUE; 
    ) 
    
    // Это очередь сообщений приложения 
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_PAINT() 
    ON_WM_LBUTTONDOWN() 
    ON_COMMAND(IDM_THREAD, OnThread) 
    ON_COMMAND(IDM_EXIT, OnExit) 
    
    ON_COMMAND(IDM_HELP, OnHelp) 
    END_MESSAGE_MAP() 
    // Вывод на экран содержимого виртуального окна 
    afx msg void CMainHin::OnPaint() 
    { 
    CPaintDC DC (this) ; 
    DC.BitBltfO, 0, гпахл, тахУ, &m_memDC, О, О, SRCCOPY) ; 
    } 
    // Изменение координат позиции для вывода 
    // при щелчке левой кнопки мыши 
    afx_msg void CMainMin::OnLButtonDown(UINT Flags, CPoint Loc) 
    { 
    m_X = Loc.x; 
    m Y = Loc.y; 
    } 
    
    // Обработка сообщения IDM_EXIT 
    afx msg void CMainWin::OnExit () 
    ( 
    int response; 
    response = MessageBox ("Quit the Program?", "Exit", MB_YESNO) ; 
    
    if(response == IDYES)                                               ! 
    SendMessage(WM CLOSE); // завершение приложения                 ' 
    } 
    // Обработка сообщения IDM HELP 


    390                                     MFC: основы программирования 


    afx msg void CMainWin::OnHelp() 
    ( 
    MessageBox("Event Object", "Help"); 
    
    // Обработка сообщения IDM THREAD 
    afx_msg void CMainWin::OnThread() 
    ( 
    AfxBeginThread(MyThreadI, this) ; 
    AfxBeginThread(MyThread2, this) ; 
    i 
    // Первый рабочий поток 
    UINT MyThreadI(LPVOID MinObjPtr) 
    ( 
    int i; 
    TEXTMETRIC tm; 
    char str[255] ; 
    int Tid = FirstThreadID; 
    FirstThreadID++; 
    CMainMin *ptr = (CMainMin *) WinObjPtr; 
    
    // получение метрик текста 
    ptr->m memDC.GetTextMetrics(&tm) ; 
    CSingleLock SyncOb(&(ptr->Event)) ; 
    SyncOb.Lock (); // ожидание события 
    
    for(i=0; i<10; i++) { 
    Sleep (500) ; 
    wsprintf(str, "First Thread #%d, beep #%d", Tid, i) ; 
    ptr->m_memDC.TextOut(ptr->m_X, ptr->m_Y, str, strlen (str)) ; 
    ptr->m Y = ptr->m_Y + tm.tmHeight + tm.tmExternalLeading; 
    ptr->InvalidateRect(NULL) ; 
    MessageBeep(MB_OK) ; 
    ) 
    FirstThreadID--; 
    SyncOb.Unlock (); // освобождение объекта события 
    return 0; 
    } 
    // Второй рабочий поток 
    UINT MyThread2(LPVOID WinObjPtr) 
    ( 
    int i; 
    TEXTMETRIC tm; 
    char str[255] ; 
    inb Tid = SecondThreadID; 


    74. Потоковая многозадачность                                       391 


    SecondThreadID++;                                                 В' 
    CMainWin *ptr = (CMainWin *) WinObjPtr;                           | 
    
    // получение метрик текста                                        1 
    ptr->ni_memDC . GetTextMetrics (?tm) ;                                  { 
    [ 
    
    for(i=0; i<10; i++) {                                              | 
    Sleep(200);                                                    j 
    wsprintf(str, "Second Thread #%d, beep #%d", Tid, i);          ' 
    ptr->m_memDC.TextOut(ptr->m_X, ptr->m_Y, str, strlen (str));    ; 
    ptr->m_Y = ptr->m_Y + tm.tmHeight + tm.tmExternalLeading; 
    ptr->InvalidateRect(NULL); 
    
    MessageBeep(MB_OK);                                            , 
    }                                                                  :. 
    SecondThreadID--;                                                  : 
    ptr->Event.SetEvent (); // сигнализирует о событии 
    return 0; 
    } 
    САрр Арр; // создание экземпляра приложения                          ' 

    В данной главе была рассмотрена лишь часть подсистем и возможностей 
интерфейса Win32, связанных с многозадачностью и синхронизацией. В числе 
других возможностей интерфейса — задание и изменение приоритетов потоков и 
процессов. Приоритет задачи, в частности, определяет, какая часть времени 
работы процессора предоставляется данной задаче. Другой возможностью, с 
которой стоит поэкспериментировать, является использование семафоров (или 
других объектов синхрониза- ; ции) как средства взаимодействия между 
процессами. Наконец, заслуживает внимания использование критических разделов. 
В некоторых ситуациях они позволяют более эффективно организовать работу с потоками. 
    
    392                                    MFC: основы программирования