85780

Синхронизация потоков с использованием МFС

Лабораторная работа

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

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

Русский

2015-03-30

123.05 KB

5 чел.

21

Лабораторная работа № 3. Синхронизация потоков с использованием МFС 

  1.  Задача синхронизации потоков

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

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

1) cостояние гонки  (race condition)  - ошибка при разработке многопоточного приложения, при которой результат его работы зависит от того, в каком порядке выполняются части кода,

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

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

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

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

В зависимости от ситуации потоки могут находиться в трех состояниях.

1. Состояние активности. Поток выполняется, ему выделено процессорное время.

2. Состояние неактивности (готовности). Поток ожидает выделения процессора, т.е. находится в состоянии готовности.

3. Состояние блокировки. Когда поток заблокирован, ему вообще не выделяется время. Обычно блокировка ставится на время ожидания какого-либо события. При возникновении этого события поток автоматически переводится из состояния блокировки в состояние готовности.

По назначению и способу выполнения выделяют два вида потоков:

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

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

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

Объектов синхронизации существует несколько, самые важные из них – это:

1) взаимоисключение (mutex, сокр. от mutual exclusion - взаимное исклю-
чение)
,

2) критическая секция (critical section),

3) событие (event) и

4) семафор (semaphore).

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

Мьютекс (mutex, сокр. от mutually exclusive - взаимоисключающий) -  объект, который может в любой момент времени принадлежать лишь одному потоку, гарантируя безопасность доступа к связанному с ним ресурсу. Для этого поток должен «заблокировать» мьютекс. Если мьютекс уже заблокирован другим потоком, то эта операция ждет, пока мьютекс не будет освобожден («разблокирован»). Таким образом, гарантируется, что только один поток имеет доступ к разделяемому ресурсу в один момент времени. \

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

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

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

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

Чтобы создать тот или иной объект синхронизации, производится вызов специальной функции WinAPI типа Create... (напр. CreateMutex). Этот вызов возвращает дескриптор объекта (HANDLE), который может использоваться всеми потоками, принадлежащими данному процессу. Есть возможность получить доступ к объекту синхронизации из другого процесса - либо унаследовав дескриптор этого объекта, либо, что предпочтительнее, воспользовавшись вызовом функции открытия объекта (Open...). После этого вызова процесс получит дескриптор, который в дальнейшем можно использовать для работы с объектом. Объекту, если только он не предназначен для использования внутри одного процесса, обязательно присваивается имя. Имена всех объектов должны быть различны (даже если они разного типа). Нельзя, например, создать событие и семафор с одним и тем же именем.

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

 DWORD WaitForSingleObject(

  HANDLE hObject,    // идентификатор объекта 

  DWORD  dwTimeout); // время ожидания в миллисекундах

Функция принимает два параметра, первый из которых HANDLE hObject - дескриптор объекта, второй dwTimeout - время ожидания в мсек.

Функция возвращает следующие строковые значения:

1) WAIT_OBJECT_0, в случае перехода объекта в сигнальном состояние,

2) WAIT_TIMEOUT - если истекло время ожидания (объект не получил сигнал), и

3)WAIT_ABANDONED, если объект-взаимоисключение не был освобожден до того, как владеющий им поток завершился.

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

Если необходимо узнавать о состоянии сразу нескольких объектов, следует воспользоваться функцией WaitForMultipleObjects.

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

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

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

  1.  Создание и синхронизация потоков с использованием MFC

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

  1.  CWinThread
  2.  CSyncObject
  3.  CEvent
  4.  CCriticalSection
  5.  CMutex
  6.  CSemaphore
  7.  CSingleLock
  8.  CMultiLock

` Поток – отдельная ветвь выполнения программы. Он имеет свой стек и работает независимо от других потоков приложения.

Класс CWinThread представляет отдельный поток. Он присутствует во всех приложениях, потому что класс CWinApp (базовый для класса DirectDrawApp) является производным от CWinThread. Этот экземпляр класса CWinThread представляет основной поток приложения. Для того, чтобы добавить новые рабочие потоки, следует создать объекты CWinThread.

Класс CSyncObject является виртуальным. Непосредственное создание экземпляров этого класса не разрешается; он существует лишь для того, чтобы обеспечивать функциональные возможности производных классов. Класс CSyncObject является базовым для классов CEvent, CCriticalSection, CMutex и CSemaphore.

Классы CSingleLock и CMultiLock применяются для блокировки потоков по состоянию одного или нескольких событий. Класс CSingleLock блокирует поток до установки конкретного события, а CMultiLock — до установки одного или всех событий из заданного набора.

Cоздание простейшего потока:

AfxBeginThread(ProcName,param,priority)

ProcName – имя функции, которая будет выполнятся в новом потоке

param – указатель типа LPVOID или void* на аргумент ProcName

priority – константа, определяющая приоритет нового потока по отношению к основному

Может принимать одно из следующих значений:

THREAD_PRIORITY_ABOVE_NORMAL // на один пункт ниже нормального

THREAD_PRIORITY_BELOW_NORMAL //на один пункт выше нормального

THREAD_PRIORITY_HIGHEST //на два пункта выше нормального

THREAD_PRIORITY_IDLE //базовый приоритет равный 1

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

THREAD_PRIORITY_NORMAL //нормальный приоритет

THREAD_PRIORITY_TIME_CRITICAL //приоритет, равный 15

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

Пример создания потоковой функции.

UINT ThreadProc(LPVOID param) //Создание потоковой функции

{::MessageBox((HWND)param, ”Thread activated”,”Message from Thread” ,MB_OK);return 0;}

Пример запуска нового потока.

SomeFunc(){ AfxBeginThread(ThreadProc,GetSafeHwnd()); //Запуск потока

}

Рассмотрим методы синхронизация работы потоков.

1. Синхронизация с использованием глобальной переменной

Пример программы.

bool bThreadstop; //контрольная переменная

UINT ThreadProc(LPVOID param) //Создание потоковой функции

{::MessageBox((HWND)param, ”Thread activated”,”Message from thread”,MB_OK);

while(!bThreadstop) {//Выполнение операций

}

::MessageBox((HWND)param, ”Thread ended”,”Message from thread”,MB_OK);

return 0;

}

SomeFunc(){ bThreadstop=false;

AfxBeginThread(ThreadProc,GetSafeHwnd()); //Запуск потока

}

StopThread(){bThreadstop=true; //остановка потока

}
2. Взаимодействие потоков с помощью сообщений

Пример программы.

const WM_THREADENDED = WM_USER+1; /Добавить в кадры сообщений

afx_msg LONG OnThreadEnded(WPARAM wParam,LPARAM lParam);

ON_MESSAGE(WM_THREADENDDED,OnThreadEnded)

bool bThreadstop; //контрольная переменная 

UINT ThreadProc(LPVOID param) //Создание потоковой функции 

{::MessageBox((HWND)param, ”Thread activated”,”Message from thread”,MB_OK);

while(!bThreadstop)

{//Выполнение опреаций

}

::PostMessage((HWND)param,WM_THREADENDED,(WPARAM)param,0); //Сообщение

return 0;

}

SomeFunc(){bThreadstop=false;
AfxBeginThread(ThreadProc,GetSafeHwnd()); //
Запуск потока

}

StopThread(){bThreadstop=true; //остановка потока}

LONG OnThreadEnded(WPARAM wParam, LPARAM lParam)
{
::MessageBox((HWND)wParam,”Thread Ended”,”Message from thread”, MB_OK);

}

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

3. Взаимодействие с помощью объектов событий

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

CEvent ThreadStart; //объект автоматически устанавливается в состояние молчания

ThreadStart.SetEvent(); //установка состояния сигнализации

Отслеживание состояния объекта осуществляется с помощью функции WinAPI WaitForSingleObject();

:: WaitForSingleObject(ThreadStart.m_hObject,INFINITE);

- первый параметр – дескриптор отслеживаемого события.

- второй параметр – время отслеживания. INFINITE – бесконечно.

В момент установки события WaitForSingleObject() вернет управление потоку.

В момент сброса события поток должен прекратить свою работу.

Для этого надо организовать постоянный опрос состояния события.

Это можно сделать следующим способом:

::WaitForSingleObject(ThreadStart.m_hObject,0);

Время 0 говорит о том, что надо опросить событие.

Если результат вызова этой функции равен WAIT_OBJECT_0, то объект в остоянии сигнализации. В других случаях – молчит.

CEvent ThreadStart; //Объект начала работы потока

CEvent ThreadEnd; //Объект окончания работы потока

UINT ThreadProc(LPVOID param) //Создание потоковой функции

{:: WaitForSingleObject(ThreadStart.m_hObject,INFINITE); //ожидание запуска потока

::MessageBox((HWND)param, ”Thread activated”,”Message from thread”,MB_OK);

bool Running=true;

int result;

while(Running){//Выполнение опреаций

result=:: WaitForSingleObject(ThreadEnd.m_hObject,0); //проверка завершения

if(result==WAIT_OBJECT_0)

Running=false;

}

::PostMessage((HWND)param,WM_CLOSE,0,0); //Сообщение

return 0;

}

Start() //запуск потока

{ThreadStart.SetEvent();

}

End() //завершение потока

{ThreadEnd.SetEvent();}

Поток нужно создавать независимо от состояния событий.

Синхронизация работы нескольких потоков

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

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

Критические секции используются для контроля доступа к защищенным данным.

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

class cls

{private:

int val;

CCriticalSection section;

public:

void write(int);

void read(int*);

}

void cls::write(int n)

{section.Lock(); val=n; section.Unlock(); }

void cls::read(int * n) {section.Lock(); *n=val; section.Unlock(); }

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

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

2. Мьютексы.

Использование защелок CMutex при синхронизации потоков одного приложения не отличается от использования критических секций. Работа с ними осуществляется с использованием следующих объектов: CSingleLock и CMultiLock. Для получения доступа к защелке используется методо Lock(). Для освобождения защелки нужно вызвать метод Unlock()

CMutex mutex; //создание защелки

CSingleLock sLock(&mutex); //захват защелки

sLock.Lock();

sLock.Unlock(); //освобождение защелки

class cls

{private:

int val;

CMutex mutex;

public:

void write(int);

void read(int*);

}

void cls::write(int n)

{CSingleLock sLock(&mutex);

sLock.Lock();

val=n;

}

void cls::read(int * n)

{CSingleLock sLock(&mutex);

sLock.Lock();

*n=val;

}

Использование метода Unlock() в данном случае необязательно т.к. при вызове деструктора sLock защелка автоматически освобождается.

3. Семафор

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

CSemaphore semaphore(2,2); //создание семафора. При создании семафора указывается начальное и максимальное значение счетчика

CSingleLock sLock(&semaphore); //Захват семафора

sLock.Lock();

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

sLock.Unlock(); //Освобождение семафора

Пример 1.  Рассмотрим консольное приложение, организующее взаимодействие двух потоков, которые задаются попеременным захватом и освобождением мьютексов  1 и  2. Команда cout.flush()переводит строки в поток, предварительно очищая буфер. Общее время обозначено SumTime. Приложение работает до тех пор, пока SumTime < 10000 мкс. Если проект не запускается, то необходимо выполнить следующую последовательность действий:

Проект -> свойства: (самая нижняя строка)-> раскрываем вкладку свойства конфигурации-> Общие. Справа в графе " набор символов" выбираем параметр "не задано"

#include "stdafx.h"

#include <windows.h>

#include <iostream>

#include <fstream>

#include <iomanip>

#include <stdlib.h>

#include <string>

#include <conio.h>

#include <stdio.h>

using namespace std;

int SumTime;

void KeepMutex(HANDLE mutex,int Time); // заголовок функции KeepMutex

void main()

{

   int i;

   DWORD res;

   // создаем объекты-взаимоисключения

   HANDLE mutex1 = CreateMutex(NULL, FALSE, "APPNAME-MTX01");

   HANDLE mutex2 = CreateMutex(NULL, FALSE, "APPNAME-MTX01");

   srand ((unsigned) time (NULL));// установка псевдослучайного генератораж

   

   // в течение 10 секунд пытаемся попеременно захватывать объекты mutex1 и mutex2

   while (SumTime<10000){

cout<<"Trying to get mutex1\n"; cout.flush();//

res = WaitForSingleObject(mutex2,3000);

             if (res == WAIT_OBJECT_0) // если захват удался

     {KeepMutex(mutex1,1000);};  //вызов функции, эмулирующей захват объекта

  cout<<"Trying to get mutex2\n"; cout.flush();

  res = WaitForSingleObject(mutex1,20000);

              if (res == WAIT_OBJECT_0) // если захват удался

      {KeepMutex(mutex2,2000);};

   cout<<"SumTime="<<SumTime<<"\n\n";

    };

    cout<<"FINISH!!";

    CloseHandle(mutex1);// закрываем дескрипторы

    CloseHandle(mutex2);

    system("pause");//запрос на завершение работы приложения

};

void KeepMutex(HANDLE mutex,int Time)

{  

 int T;

  T=rand()*Time/(RAND_MAX+1);//расчет текущего времени ожидания

  Sleep(T); // ждем 

  cout<<"Got it! Waiting for mutex"<<"Time="<<T<<"\n"; cout.flush();

  cout<<"Now releasing the mutex"<<"\n"; cout.flush();// освобождаем объект

  ReleaseMutex(mutex);

  SumTime+=T; //пересчет общего времени

}

Задания:

  1.  Модифицировать текст программы таким образом, чтобы в ней использовались 3 мьютекса, которые бы удерживали процесс в порядке 1-2-3-1-…
  2.  Сократить число мьютексов до одного.
  3.  Добавить печать суммарного времени для каждого обработки мьютекса.

  1.  Создание и выполнение потоков

. В случае с CreateThread

Функция CreateThread создает поток, который выполняется в пределах виртуального адресного пространства вызывающего процесса.

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

Синтаксис

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes// дескриптор защиты
SIZE_T
 dwStackSize,                       // начальный размер стека
LPTHREAD_START_ROUTINE
 lpStartAddress,    // функция потока
LPVOID
 lpParameter,                       // параметр потока
DWORD
 dwCreationFlags,                    // опции создания
LPDWORD
 lpThreadId                        // идентификатор потока

);

Параметры

lpThreadAttributes

[in] Указатель на структуру SECURITY_ATTRIBUTES, которая обуславливает, может ли возвращенный дескриптор быть унаследован дочерними процессами. Если lpThreadAttributes является значением ПУСТО (NULL), дескриптор не может быть унаследован.

Windows NT/2000/XP: член структуры lpSecurityDescriptor определяет дескриптор безопасности для нового потока. Если lpThreadAttributes имеет значение ПУСТО (NULL), поток получает заданный по умолчанию дескриптор защиты. Списки контроля доступа (ACL) в заданном по умолчанию дескрипторе безопасности для потока поступают из первичного маркера или маркера заимствования прав создателя.

dwStackSize

[in] Начальный размер стека, в байтах. Система округляет это значение до самой близкой страницы памяти. Если это значение нулевое, новый поток использует по умолчанию размер стека исполняемой программы. Дополнительную информацию см. в статье Размер стека потока

Обратите внимание! на то, что, в случае необходимости, размер стека растет.

lpStartAddress

[in] Указатель на определяемую программой функцию типа LPTHREAD_START_ROUTINE, код которой исполняется потоком и обозначает начальный адрес потока. Для получения дополнительной информации о функции потока, см. ThreadProc.

lpParameter

[in] Указатель на переменную, которая передается в поток.

dwCreationFlags

[in] Флажки, которые управляют созданием потока. Если установлен флажок CREATE_SUSPENDED, создается поток в состоянии ожидания и не запускается до тех пор, пока не будет вызвана функцияResumeThread. Если это значение нулевое, поток запускается немедленно после создания. В это время, никакие другие значения не поддерживаются.

Windows XP: Если установлен флажок STACK_SIZE_PARAM_IS_A_RESERVATION, параметр dwStackSize задает начальный резервный размер стека. Иначе, dwStackSize устанавливает фиксированный размер.

lpThreadId

[out] Указатель на переменную, которая принимает идентификатор потока.

Windows NT /2000/XP: Если этот параметр имеет значение ПУСТО (NULL), идентификатор потока не возвращается.

Windows 95/98/Me: Этот параметр не может быть значением ПУСТО (NULL).

Возвращаемые значения

Если функция завершается успешно, величина возвращаемого значения - дескриптор нового потока.

Если функция завершается с ошибкой, величина возвращаемого значения - ПУСТО (NULL). Чтобы получать дополнительные данные об ошибках, вызовите GetLastError.

Обратите внимание!, что функция CreateThread может завершиться успешно, даже если lpStartAddress указывает на данные, код или не понятно куда. Если ее начальный адрес ошибочен, когда поток запускается, происходит исключительная ситуация и поток заканчивает работу. Завершение работы потока в результате недопустимого начального адреса обрабатывается как выход из-за ошибки для процесса потока. Это поведение похоже на асинхронный характер CreateProcess, где процесс создается, даже если он адресуется к ошибочным или отсутствующим библиотекам динамической связи (DLL).

Windows 95/98/Me: функция CreateThread завершается успешно, только тогда, когда она вызвана в контексте 32-разрядной программы. 32-разрядная DLL не может создать дополнительный поток, когда эта DLL вызывается 16-разрядной программой.

Замечания

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

Новый дескриптор потока создается с правами доступа THREAD_ALL_ACCESS. Если дескриптор безопасности не предоставляется, этот дескриптор может быть использован в любой функции, которая требует дескриптора объекта потока. Когда дескриптор безопасности предоставлен, проверка доступа выполняется для всех последующих использованиях дескриптора прежде, чем этот доступ предоставляется. Если проверка доступа не запрещает доступ, запрашивающий процесс не может использовать дескриптор, чтобы получить доступ к потоку. Если поток исполняет роль клиента, то он вызывает CreateThread с дескриптором безопасности имеющим значение ПУСТО (NULL), созданный объект потока имеет заданный по умолчанию дескриптор безопасности, который позволяет доступ только владельцу маркера заимствования прав или членам TokenDefaultDacl. Для получения дополнительной информации, см. статью Защита потока и права доступа.

Выполнение потока начинается в функции, указанной параметром lpStartAddress . Если эта функция возвращает значение, величина возвращаемого значения ДВОЙНОЕ СЛОВО (DWORD) используется, чтобы завершить работу потока неявным вызовом функции ExitThread. Чтобы получить величину возвращаемого значения потока, используйте функцию GetExitCodeThread.

Поток создается с приоритетом потока THREAD_PRIORITY_NORMAL. Используйте функции GetThreadPriority и SetThreadPriority, чтобы получать и установить приоритетное значение потока.

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

Объект потока остается в системе, до тех пор, пока не поток закончит работу, и все дескрипторы к нему не будут закрыты через вызов CloseHandle.

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

  1.  В ходе запуска процесса и процедуры инициализации DLL, новые потоки могут быть созданы, но они не начинают исполнение кода до тех пор, пока не будет сделана инициализация DLL для процесса.
  2.  Одновременно только один поток в процессе может быть инициализирован или отключен процедурой в DLL.
  3.  Функция ExitProcess не возвращает значения до тех пор, пока в потоках не отработают в их DLL процедуры инициализации или отключения.

Поток, который использует функции из библиотек этапа исполнения C, для управления потоком должен использовать C - функции этапа исполнения beginthread и endthread, а не CreateThread иExitThread. Когда вызывается ExitThread, происходит утечка ресурсов в маленькой памяти, что приводит к сбою в программе.

hromjo -> Создание потоков средствами C++ (18.07.2010 19:42:31)


Создание потоков средствами C++ 

Знаю что статья фигня, кому не нравится говорите, нахрен удалю. Просто может кому-то поможет(
уже стихами говорю ;)

Сегодня я хотел бы рассказать как собственно создавать потоки функциями _beginthread и _beginthreadex. Чем же лучше использование этих функций в замен стандартных средств из winapi? 

Начну с того, что они позволяют использовать внутри потока все средства CRT-библиотеки, говоря простым языком, то можно использовать функции C++ такие как cout, fprint и т.д. В случае с CreateThread, которую рассматривать я не буду, можно использовать только WinApi средства. 

Для начала рассмотрим прототип первой функции: 

uintptr_t _beginthread( 
void( *start_address )( void * ), 
unsigned stack_size, 
void *arglist 
); 

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

void myThread(void* pParams) 

. . . 


Имя конечно же может быть любое. 

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

С третьим все ещё легче, это аргументы функции потока, которые вполне могут быть как NULL. Или же выглядеть как (void*)(pParam) в случае с первой функцией. 

Для полности приведу пример, который выводит 100 раз цифру 1 и 100 раз цифру 2. 

#include <iostream> 
#include <process.h> 

void myThread(void* pParams) 

for(int f=0;f<=100;f++){ 
std::cout << “1″; 

_endthread(); 


int main() 

_beginthread(myThread, 0, NULL); 
for(int q=0;q<=100;q++){ 
std::cout << “2″; 


system(“PAUSE”); 


Теперь пора перейти к более функциональному собрату первой _beginthreadex. Она нужна в случае если вам понадобится в дополнение и возможности CreateThread. К примеру приостановление потока, чего нету в обычной _beginthread. Посмотрим на прототип: 

unsigned **** _beginthreadex( 
void *Security, 
unsigned StackSize, 
unsigned (*StartAddress)(void*), 
void *ArgList, 
unsigned Initflag, 
unsigned *ThrdAddr); 

Теперь посмотрим на её аргументы: 

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

Второй аргумент это адрес стека. О нем речь шла ещё в первой функции. 

Третий – сама функция задачи потока. О её уже было опять же рассказано в первой функции. 

Четвертый – тоже уже было. см. _beginthread. 

Пятый – это уже интереснее. Он используется для установки параметров задачи. Например если вы хотите создать поток в приостановленном состояние, то вам нужно установить параметр CREATE_SUSPENDED. Если не нужен можно поставить 0. 

Ну и последний это адрес создания задачи. 

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

Функция WaitForSingleObject

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

Для ввода извещающие состояние ожидания, используйте WaitForSingleObjectEx функцию. Ждать несколько объектов, использовать WaitForMultipleObjects .

 DWORD WINAPI WaitForSingleObject (

  _In_ HANDLE hHandle,

  _In_ DWORD dwMilliseconds);

Параметры hHandle 

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

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

Ручка должна иметь право доступа SYNCHRONIZE. Для получения дополнительной информации см. прав Стандартный доступа .

dwMilliseconds.

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

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

Возвращаемое значение

Функция list

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

#include <list>

Функция time.h

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

#include<time.h> 

Функция assert

Макрос assert(), определенный в заголовке <assert.h>, записывает информацию об ошибке в поток stderr, а затем прекращает выполнение программы, если выражение ехр равно нулю. В противном случае макрос assert() никаких действий не выполняет.

#include <assert.h>

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

Пример 2. Пример с использованием Mutex, в котором два потока заполняют очередь.

#include "stdafx.h"

#include <windows.h>

#include <iostream>

#include <fstream>

#include <iomanip>

#include <stdlib.h>

#include <string>

#include <conio.h>

#include <stdio.h>

#include <list>

#include <time.h>

#include <assert.h>

#include <queue> 

using namespace std;

 typedef time_t;

HANDLE   hMutex;

std::list<int> lstQueue;

 double dtime;

 queue<double> myQueue1;

 queue<double> myQueue2;

 

  time_t start;

  time_t end;

DWORD WINAPI ThreadProc_1(PVOID pParam)

{

 int val = *reinterpret_cast<int*>(pParam);

 int Time =100;

 

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

{

 WaitForSingleObject(hMutex, INFINITE);

 lstQueue.push_back(val);

 time_t start = time(NULL);

      assert(start != (time_t)(-1));//vot

 ReleaseMutex(hMutex);

  int T=rand()*Time/(RAND_MAX+1);//расчет текущего времени ожидания

 Sleep(100*T);        //Просто задержка для наглядности работы

 

 time_t end = time(NULL);

 //Возвращает текущее календарное время или −1, если это время не известно. Если указатель tp не равен NULL, то возвращаемое значение записывается также и в *tp

assert(end != (time_t)(-1));//vot

dtime = difftime(end,start);

//void queue::qinsert(double dtime);

myQueue1.push(dtime);

cout<<" step = "<<i<<" T 1 = "<<T<<"   dtime = "<<dtime<<endl;

}

 return 0;

}

 

DWORD WINAPI ThreadProc_2(PVOID pParam)

{

 int val = *reinterpret_cast<int*>(pParam);

 int Time =50;

 

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

{

 WaitForSingleObject(hMutex, INFINITE);

 lstQueue.push_back(val);

 time_t start = time(NULL);

      assert(start != (time_t)(-1));//vot

 ReleaseMutex(hMutex);

  int T=rand()*Time/(RAND_MAX+1);//расчет текущего времени ожидания

 Sleep(100*T);        //Просто задержка для наглядности работы

 

 time_t end = time(NULL);

 //Возвращает текущее календарное время или −1, если это время не известно. Если указатель tp не равен NULL, то возвращаемое значение записывается также и в *tp

assert(end != (time_t)(-1));//vot

dtime = difftime(end,start);

myQueue2.push(dtime);

cout<<" step = "<<i<<" T 2 = "<<T<<"   dtime = "<<dtime<<endl;

}

 return 0;

}

 

int _tmain(int argc, _TCHAR* argv[])

{

 //Создаем объекты синхронизации

hMutex = CreateMutex(NULL, FALSE, NULL);

 

 //запускаем поток

 DWORD dwID;

 int   iVal1 = 1, iVal2 = 2;

 

 //Начинаем заполнять очередь.

 HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc_1, &iVal1, 0, &dwID);

 HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc_2, &iVal2, 0, &dwID);

 

 //Ждем завершеня потоков

 HANDLE hEvents[2] = {hThread1, hThread2};

WaitForMultipleObjects(2, hEvents, TRUE, INFINITE);

 

 //Печать результата 

 int mumber1 = myQueue1.size();

 int mumber2 = myQueue2.size();

 for(int kk = 0; kk < mumber1; ++kk){

cout << kk<<"Element"<<" myQueue1 = "<<myQueue1.front()<<endl;

myQueue1.pop();

}

cout << "------------------------"<<endl;

 for(int kkk = 0; kkk < mumber2; ++kkk){

cout << kkk<<"Element"<<" myQueue2 = "<<myQueue2.front()<<endl;

myQueue2.pop();

}

 //for(std::list<int>::iterator it = lstQueue.begin(); it != lstQueue.end(); it++)

 //{

 // std::cout <<"  *it = "<< *it <<"  dtime = "<<dtime<< ", "<<endl; //печатаем результат

 //}

 

CloseHandle(hThread1);

CloseHandle(hThread2);

CloseHandle(hMutex);

 system("pause");//запрос на завершение работы приложения

 return 0;

} 

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

Задания:

1.Рассмотреть  вариант данного примера без синхронизации.

2. Сократить для данного примера число потоков до одного.

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

#include "stdafx.h"

#include <windows.h>

#include <iostream>

#include <fstream>

#include <iomanip>

#include <stdlib.h>

#include <string>

#include <conio.h>

#include <stdio.h>

using namespace std;

HANDLE   hReadyForProcessing;

HANDLE   hDataReady;

HANDLE   hTerminate;

int    iResult = 0;

 

 

DWORD WINAPI ThreadProc(PVOID pPararn)

{

 HANDLE hEvents[2] = {hReadyForProcessing, hTerminate};

 DWORD dwRes;

 while(WAIT_OBJECT_0 + 0 == (dwRes = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE)) )

{

 iResult = iResult * 10;

 SetEvent(hDataReady);

}

 if(WAIT_OBJECT_0 + 1 != dwRes) //Условие завершения

{

 return 2;//Что то не так с вызовом WaitForMultipleObjects

 }

 return 0;

}

 

int _tmain(int argc, _TCHAR* argv[])

{

 //Создаем объекты синхронизации

hReadyForProcessing = CreateEvent(NULL, FALSE, FALSE, NULL);

hDataReady = CreateEvent(NULL, FALSE, FALSE, NULL);

hTerminate = CreateEvent(NULL, FALSE, FALSE, NULL);

 //запускаем поток

 DWORD dwID;

 HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &dwID);

 

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

{

 iResult = 10*(i+1);

 SetEvent(hReadyForProcessing);    //указываем потоку, что есть данные для обработки

 WaitForSingleObject(hDataReady, INFINITE);

 

 /*

 if(10000 != iResult)

 {

  std::cout << "error" << std::endl;

 }

 */

 std::cout <<" i="<<i<<" iResult = "<< iResult << std::endl; //печатаем результат

 }

 

SetEvent(hTerminate);     //запрос на завершение потока

 if(WAIT_OBJECT_0 != WaitForSingleObject(hThread, 5000))

{

 TerminateThread(hThread, 3);

 std::cout << "Error in thread" << std::endl;

}else

{

 CloseHandle(hThread);

}

CloseHandle(hDataReady);

CloseHandle(hReadyForProcessing);

CloseHandle(hTerminate);

system("pause");//запрос на завершение работы приложения

 

 return 0;

}

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

Задания:

1.В функции ThreadProc установить случайное увеличение параметра и iResult.

2. Увеличить количество потоков с одного до двух.

Использование семафора при помощи API-функций

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

CreateSemaphor - создаёт глобальный объект-семафор. Возвращает дескриптор семафора.

HANDLE CreateSemaphore

(

 LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,

LONG lInitialCount,

LONG lMaximumCount,

 LPCTSTR lpName)

Первый параметр - указатель на структуру, определяющую атрибуты доступа. Может иметь значение для Windows NT. Обычно данный параметр равен NULL.
Второй параметр - начальное значение счётчика семафора. Определяет, сколько задач имеют доступ к ресурсу в начале.
Третий параметр - количество задач, которые имеют одновременный доступ к ресурсу.
Четвёртый параметр - указатель на строку, содержащую имя семафора.

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

HANDLE OpenSemaphore

(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

 LPCTSTR lpName)

Параметры функции: первый параметр - определяет желаемый уровень доступа к семафору. Возможны значения:

-SEMAPHORE_MODiFY_STATE = 2H, разрешить использование функции ReleaseSemaphore,

- SYNCHRONIZE = 100000H, разрешить использование любой функции ожидания, только для Windows NT,

- SEMAPHORE_ALLACCESS = 0F0000h+ SYNCHRONIZE+3H, специфицирует все возможные флаги доступа к семафору.

Для контроля ожидания открытия семафора используется функция WaitForSingleObject.

DWORD WaitForSingleObject

(

HANDLE hHandle,

DWORD dwMilliseconds

)

Первый параметр – дескриптор семафора.
Второй параметр – время ожидания в миллисекундах. Если параметр равен INFINITE = 0FFFFFFFFh, то время ожидания не ограничено.

При успешном завершении, т. е. открытии доступа к объекту, функция возвращает 0. Значение 102h будет означать, что заданный период ожидания не завершен.

ReleaseSemaphore – функция освобождения семафора, которая позволяет получить доступ к ресурсу другим процессам.

BOOL ReleaseSemaphore

(

HANDLE hSemaphore,

LONG lReleaseCount,

LPLONG lpPreviousCount

)

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

Рассмотрим алгоритм работы с семафором. Сначала при помощи функции CreateSemaphore создадим семафор, его дескриптор присваивается глобальной переменной. Перед попыткой обращения к ресурсам доступ, к которым необходимо ограничить, поток должен вызвать функцию WaitForSingleObject. При открытии доступа функция возвращает 0. По окончании работы с ресурсом следует вызвать функцию ReleaseSemaphor. Тем самым увеличивается счётчик доступа на 1. С помощью семафора можно регулировать количество потоков, которые одновременно могут иметь доступ к ресурсу. Максимальное значение счетчика как раз и определяет, сколько потоков могут получить доступ к ресурсу одновременно. Но обычно, как мы уже говорили, максимальное значение полагают равным 1.

Пример 4

// TestSemaphore.cpp : Defines the entry point for the console application.

//

#include "stdafx.h"

#include <windows.h>

#include <iostream>

#include <fstream>

#include <iomanip>

#include <stdlib.h>

#include <string>

#include <conio.h>

#include <stdio.h>

#include <list>

#include <time.h>

#include <assert.h>

#include <queue>

#include "process.h" // vot ito

#include <math.h>

using namespace std;

HANDLE hSemaphore;

LONG cMax = 2;

void Test1(void *);

void Test2(void *);

void Test3(void *);

void main()

{

hSemaphore = CreateSemaphore(

 NULL,// нет атрибута

 cMax,// начальное состояние

cMax,// максимальное состояние

 NULL// без имени

);

if (!hSemaphore == NULL)

{

if (_beginthread(Test1,1024,NULL)==-1)

cout << "Error begin thread " << endl;

if (_beginthread(Test2,1024,NULL)==-1)

cout << "Error begin thread " << endl;

if (_beginthread(Test3,1024,NULL)==-1)

cout << "Error begin thread " << endl;

Sleep(10000);

CloseHandle(hSemaphore);

}

else

cout << "error create semaphore" << endl;

system("pause");//запрос на завершение работы приложения

}

void Test1(void *)

{

cout << "Test1 Running\n" << endl;

DWORD dwWaitResult=1;

while(dwWaitResult!=WAIT_OBJECT_0)

{

dwWaitResult = WaitForSingleObject(

hSemaphore,// указатель на семафор

1// интерфал ожидания

);

cout << "Test 1 TIMEOUT\n" << endl;

}

Sleep(1000);

if (ReleaseSemaphore(

hSemaphore,// указатель на светофор

1,// изменяет счетчик на 1

 NULL)

)

cout << " ReleaseSemaphore Ok Test1\n" << endl;

_endthread();

}

void Test2(void *)

{

cout << "Test2 Running\n" << endl;

DWORD dwWaitResult=1;

while(dwWaitResult!=WAIT_OBJECT_0)

{

dwWaitResult = WaitForSingleObject(hSemaphore,1);

cout << "Test 2 TIMEOUT\n" << endl;

}

Sleep(1000);

if (ReleaseSemaphore(hSemaphore,1,NULL))

cout << " ReleaseSemaphore Ok Test2\n" << endl;

_endthread();

}

void Test3(void *)

{

cout << "Test3 Running\n" << endl;

DWORD dwWaitResult=1;

while(dwWaitResult!=WAIT_OBJECT_0)

{

dwWaitResult = WaitForSingleObject(hSemaphore,1);

cout << "Test 3 TIMEOUT\n" << endl;

}

if (ReleaseSemaphore(hSemaphore,1,NULL))

cout << " ReleaseSemaphore Ok Test3\n" << endl;

_endthread();

}

void Test2(void *)

{

cout << "Test2 Running\n" << endl;

DWORD dwWaitResult;

while(dwWaitResult!=WAIT_OBJECT_0)

{

dwWaitResult = WaitForSingleObject(hSemaphore,1);

cout << "Test 2 TIMEOUT\n" << endl;

}

Sleep(1000);

if (ReleaseSemaphore(hSemaphore,1,NULL))

cout << " ReleaseSemaphore Ok Test2\n" << endl;

_endthread();

}

void Test3(void *)

{

cout << "Test2 Running\n" << endl;

DWORD dwWaitResult;

while(dwWaitResult!=WAIT_OBJECT_0)

{

dwWaitResult = WaitForSingleObject(hSemaphore,1);

cout << "Test 3 TIMEOUT\n" << endl;

}

if (ReleaseSemaphore(hSemaphore,1,NULL))

cout << " ReleaseSemaphore Ok Test3\n" << endl;

_endthread();

system("pause");//запрос на завершение работы приложения

}

Cинхронизация в MFC

Библиотека MFC содержит специальные классы для синхронизации потоков (CMutex, CEvent, CCriticalSection и CSemaphore). Эти классы соответствуют объектам синхронизации WinAPI и являются производными от класса CSyncObject. Конструкторы и методы этих классов Lock и Unlock являются обертками для объектов синхронизации.

Cинхронизация в CMutex

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

Чтобы использовать объект CMutex, создайте объект CMutex при необходимости. Укажите имя мьютекса необходимо дождаться on, и что приложение должно иметь его. Затем можно получить доступ мьютекс, когда конструктор завершает работу. Вызовите CSyncObject::Unlock по завершении при доступе к контролируемому ресурсу.

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

Доступ к ресурсам проконтролированные объектами CMutex таким образом, сначала создать переменную или типа CSingleLock или ввести CMultiLockв функции-члене доступа данного ресурса. Затем вызовите функцию-член объекта Lock блокировки (например, CSingleLock::Lock). На этом этапе в поток или получит доступ к ресурсу, ожидание ресурсов для освобождения и доступа, увеличения или ожидает ресурс для освобождения и выдаст ошибку времени ожидания, а не сумеющ для получения доступа к ресурсу. В любом случае ресурс был доступ потокобезопасным способом.Освобождение ресурс, использование функции-члена Unlock объекта блокировки (например, CSingleLock::Unlock) или разрешить объект блокировки к падению из области.

Создает именованный или неименованный объект CMutex.

CMutex(

  BOOL bInitiallyOwn = FALSE,

  LPCTSTR lpszName = NULL,

  LPSECURITY_ATTRIBUTES lpsaAttribute = NULL

);

Параметры

bInitiallyOwn

Определяет, если поток создания объекта CMutex исходной имеет доступ к ресурсу проконтролированному мьютексами.

lpszName

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

lpsaAttribute

Атрибуты безопасности для объекта мьютекса. Полное описание структуры, см. в разделе SECURITY_ATTRIBUTES в Windows SDK.

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

C классами синхронизации MFC можно работать как напрямую, используя методы Lock и Unlock, так и через промежуточные классы CSingleLock и CMultiLock (хотя на мой взгляд, работать через промежуточные классы несколько неудобно. Но использование класса СMultiLock необходимо, если вы хотите следить за состоянием сразу нескольких объектов).

Задания для самостоятельного выполнения.

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

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

3. Модифицировать текст программы примера 1 таким образом, чтобы в ней 2 мьютекса, записывали информацию раздельно на монитор и в текстовый файл.

4. Добавить для примера 2 третий  поток со средним временем выдержки Т=200. Сократить количество циклов срабатывания внутри процессов до 5.

5. Для примера 3 вставить В функцию ThreadProc выдержку выполнения потока пропорционально параметру i.

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


 

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

66863. Створення додатку для роботи з базою даних 616.5 KB
  Створення бази даних База даних буда створена у СУБД MS SQL Server 2000 згідно до обраної предметної області пункт прийому сировини шляхом генерації з фізичної моделі побудованої за допомогою Erwin згідно методичних вказівок. Отриманий результат має вигляд...
66865. GREAT BRITAIN AND THE UNITED KINGDOM. THE SYSTEM OF GOVERNMENT IN THE UK 80.5 KB
  The British Isles today are shared by two separate and independent states. The smaller of these is the Republic of Ireland, with its capital in Dublin. The larger, with London as its capital, is the United Kingdom of Great Britain and Northern Ireland.
66869. Базы и банки данных 1.05 MB
  База данных должна содержать сведения о следующих объектах: Клиенты регистрационный номер адрес телефон пол образование номер квитанции об уплате. 1 Разработка концептуальной модели Очевидно что в разрабатываемой базе данных БД предметной областью является Бюро по найму.