- •Лабораторная работа № 3. Синхронизация потоков с использованием мfс
- •Задача синхронизации потоков
- •Создание и синхронизация потоков с использованием mfc
- •1. Синхронизация с использованием глобальной переменной
- •3. Взаимодействие с помощью объектов событий
- •1. Критические секции.
- •2. Мьютексы.
- •3. Семафор
- •Создание и выполнение потоков
- •Функция WaitForSingleObject
- •Cинхронизация в mfc
- •Cинхронизация в cMutex
- •Задания для самостоятельного выполнения.
Функция 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");//запрос на завершение работы приложения }
|
