Теперь я расскажу вам, что такое критическая секция, зачем она нужна и как ее использовать. Конечно же статья будет о многопоточном программировании и увы только под Windows. Я буду использовать Windows7 x64 + VS2010 SP1.Для начала напишем очень простое приложение в несколько потоков.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <iostream> #include <windows.h> #include <process.h> void ThreadRun(void* params); //функции потока int main(int argc, char* argv[]) { for (int i = 0; i &lt; 20; ++i); std::cout << "Start thread!rn"; _beginthread(ThreadRun, 0, NULL); //функция запуска потока int a; std::cin &lt;&lt; a; //ожидание ввода, что бы не закрылось окно return 0; } void ThreadRun(void* params) { std::string line; line.append("I am a thread! Yeah!rn"); std::cout &lt;&lt; line.c_str(); } |
И так, функция _beginthread как вы наверно уже догадались создает новый поток ее прототип:
1 2 3 4 5 |
uintptr_t _beginthread( void( *start_address )( void * ), unsigned stack_size, void *arglist ); |
Параметр start_address задает функцию потока, параметр stack_size — размер стека ну и arglist — список аргументов передаваемых потоку. Если поток удачно запущен, он вернет его идентификатор(handle).
Значит так:
- Создаем функцию для потока, которая должна возвращать void и принимать в качестве аргументов void* (это для случая с _beginthread);
- В главном потоке(или в любом другом) с помощью _beginthread запускам экземпляры потоков;
Все ну очень просто.
Что же такое критическая секция? Критическая секция — это объект для синхронизации данных между потоками, то есть она не позволяет выполнять некие действия одновременно. Программист сам решает что «заключить» в критическую секцию. Как с ним работать?
1 2 3 4 5 6 7 8 9 |
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. Тем самым он уведомляет систему о том, что кабинка с данным ресурсом освободилась. Если Вы забудете это сделать, система будет считать, что ресурс все еще занят, и не позволит обратиться к нему другим ждущим потокам, То есть Вы вышли из кабинки и оставили на двери знак «занято».
Давайте создадим простую программу. В ней будет массив, и доступ к нему из разных потоков.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
#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.
- ПРОФИТ!
Удачи!