Теперь я расскажу вам, что такое критическая секция, зачем она нужна и как ее использовать. Конечно же статья будет о многопоточном программировании и увы только под Windows. Я буду использовать Windows7 x64 + VS2010 SP1.Для начала напишем очень простое приложение в несколько потоков.
#include <iostream>
#include <windows.h>
#include <process.h>
void ThreadRun(void* params); //функции потока
int main(int argc, char* argv[])
{
for (int i = 0; i < 20; ++i);
std::cout << "Start thread!rn";
_beginthread(ThreadRun, 0, NULL); //функция запуска потока
int a; std::cin << a; //ожидание ввода, что бы не закрылось окно
return 0;
}
void ThreadRun(void* params)
{
std::string line;
line.append("I am a thread! Yeah!rn");
std::cout << line.c_str();
}
И так, функция _beginthread как вы наверно уже догадались создает новый поток ее прототип:
uintptr_t _beginthread( void( *start_address )( void * ), unsigned stack_size, void *arglist );
Параметр start_address задает функцию потока, параметр stack_size — размер стека ну и arglist — список аргументов передаваемых потоку. Если поток удачно запущен, он вернет его идентификатор(handle).
Значит так:
- Создаем функцию для потока, которая должна возвращать void и принимать в качестве аргументов void* (это для случая с _beginthread);
- В главном потоке(или в любом другом) с помощью _beginthread запускам экземпляры потоков;
Все ну очень просто.
Что же такое критическая секция? Критическая секция — это объект для синхронизации данных между потоками, то есть она не позволяет выполнять некие действия одновременно. Программист сам решает что «заключить» в критическую секцию. Как с ним работать?
CRTITICAL_SECTION csFlag; //объявление критической секции ... InitializeCriticalSection(&csFlag); //инициализациия критической секции ... EnterCriticalSection(&csFlag); //функция входа в критическую секция ... LeaveCriticalSection(&csFlag); //функция выхода из критической секции ... DeleteCriticalSection(&csFlag); //Удаление(деинициализация) критической секции
В книге Джеффри Рихтера «Windows для профессионалов», автор приводит такой пример:
Так как я пишу эти строки в самолете, позвольте провести следующую аналогию. Структура CRITICAL_SECTION похожа на туалетную кабинку в самолете, а данные, которые нужно защитить, — на унитаз, Туалетная кабинка (критическая секция) в самолете очень маленькая, и единовременно в ней может находиться только один человек (поток), пользующийся унитазом (защищенным ресурсом).
Если у Вас есть ресурсы, всегда используемые вместе, Вы можете поместить их в одну кабинку — единственная структура CRITICAL_SECTION будет охранять их всех. Но если ресурсы не всегда используются вместе (например, потоки 1 и 2 работают с одним ресурсом, а потоки 1 и 3 — с другим), Вам придется создать им по отдельной кабинке, или структуре CRITICAL_SECTION.
Теперь в каждом участке кода, где Вы обращаетесь к разделяемому ресурсу, вызывайте EnterCriticaSection, передавая ей адрес структуры CRITICAL_SECTION, которая выделена для этого ресурса. Иными словами, поток, желая обратиться к ресурсу, должен сначала убедиться, нет ли на двери кабинки знака «занято». Структура CRITI CAL_SECTION идентифицирует кабинку, в которую хочет войти поток, а функция EnterCriticalSection — тот инструмент, с помощью которого он узнает, свободна или занята кабинка. EnterCriticalSection допустит вызвавший ее поток в кабинку, если определит, что та свободна. В ином случае (кабинка занята) EnterCriticalSection заставит его ждать, пока она не освободится.
Поток, покидая участок кода, где он работал с защищенным ресурсом, должен вызвать функцию LeaveCriticalSection. Тем самым он уведомляет систему о том, что кабинка с данным ресурсом освободилась. Если Вы забудете это сделать, система будет считать, что ресурс все еще занят, и не позволит обратиться к нему другим ждущим потокам, То есть Вы вышли из кабинки и оставили на двери знак «занято».
Давайте создадим простую программу. В ней будет массив, и доступ к нему из разных потоков.
#include <iostream>
#include <Windows.h>
#include <process.h>
#include <queue>
#include <time.h>
//Описание функции потока
void ThreadRun(void* params);
//Функция которая будет выводить массив в консоль
void ArrayShow(int arr[]);
//Функция будет заполнять массив случайными числами
void ArrayChange(int arr[]);
//Наш объект(массив), к которому должен быть синхронизирован доступ
int myArray[10];
//Критическая секция для массива
CRITICAL_SECTION csArray;
int main(int argc, char* argv[])
{
//Заполним массив числами
for (int i = 0; i < 10; ++i)
myArray[i] = i;
//Инициализируем критическую секцию
InitializeCriticalSection(&csArray);
std::cout << "Array before:rn";
ArrayShow(myArray);
//Запустим 5 потоков
for (int i = 0; i < 5; ++i)
{
//Каждому потоку, в качестве параметра передаем его номер
int* number = new int;
*number = i;
_beginthread(ThreadRun, 0, (void*) number);
}
//Ждем, что бы пользователь сообщил о завершении потоков
char isFinished = 'n';
while (isFinished != 'y')
{
std::cout << "MAIN_THREAD: All threads is finished? (y/n)rn";
std::cin >> isFinished;
}
//Удаляем критическую секцию
DeleteCriticalSection(&csArray);
std::cout << "Array after:rn";
ArrayShow(myArray);
//Пауза
system("PAUSE");
return 0;
}
void ThreadRun(void* params)
{
//Преобразуем входящие параметры в int*
int* myNumber = (int*) params;
std::string start;
std::string enter;
std::string leave;
char buff[10];
itoa(*myNumber, buff, 10);
start.append("Thread["); start.append(buff); start.append("] start...rn");
enter.append("Thread["); enter.append(buff); enter.append("] enter in critical section...rn");
leave.append("Thread["); leave.append(buff); leave.append("] leave critical section...rn");
std::cout << start.c_str();
//Входим в критическую секцию
EnterCriticalSection(&csArray);
std::cout << enter.c_str();
//Задержка для наглядности процесса
Sleep(1000);
ArrayChange(myArray);
ArrayShow(myArray);
//Выходим из критической секции
LeaveCriticalSection(&csArray);
std::cout << leave.c_str();
}
void ArrayShow(int arr[])
{
std::string line;
for (int i = 0; i < 10; ++i)
{
char buff[10];
itoa(arr[i], buff, 10);
line.append(buff);
line.append("-");
}
line.append("rn");
std::cout << line.c_str();
}
void ArrayChange(int arr[])
{
srand(time(NULL));
for (int i = 0; i < 10; ++i)
{
myArray[i] = rand() % 100;
}
}
Результат работы программы на изображении справа.
Как видно каждый поток по очереди изменил массив чисел, при этом никаких ошибок по поводу доступа к памяти не возникло и программа отработала корректно!
Подытожим:
- Создаем для каждого нужного объекта свою критическую секцию;
- Инициализируем секции перед тем, как работать с ними;
- Для того, что бы начать работу с объектом вызываем функцию EnterCriticalSection и передаем ей связанную(абстрактно) с объектом критическую секцию;
- После окончания работы с объектом, выходим из необходимой критической секции с помощью функции LeaveCriticalSection;
- Когда уже точно известно, что потоки больше не будут использовать критическую секцию, можем ее удалить с помощью функции DeleteCriticalSection.
- ПРОФИТ!
Удачи!
