#cpp #многопоточность #winapi #mutex
У меня есть глобальная переменная global равная 100, и есть 2 потока, каждый из которых 12 раз увеличивает значение этой глобальной переменной, т.е. в итоге её значение должно быть равным 124. Сначала я использовал для синхронизации мьютекс и всё было отлично, т.к. потоки выполнялись поочерёдно. После я решил заменить мьютекс на критическую секцию, и вроде бы я в итоге получаю 124, но при этом потоки не выполняются поочередно чего я никак не мог ожидать, может быть кто-нибудь знает в чём тут дело? Собственно код: #include "stdafx.h" #include#include #include using namespace std; int global = 100; HANDLE ht1, ht2; CRITICAL_SECTION cs; DWORD WINAPI ThreadProc1(LPVOID lpParameter ) { int i, j; for (j=1; j <= 12; j++) { EnterCriticalSection(&cs); i = global; i++; Sleep (1); global = i; printf_s( "%4s %4d \n", " 1 th", i ); LeaveCriticalSection(&cs); } return 0; } DWORD WINAPI ThreadProc2 (LPVOID lpParameter) { int i, j; for (j=1; j <= 12; j++) { EnterCriticalSection(&cs); i = global; i++; Sleep (1); global = i; printf_s( "%4s %4d %4d \n", " 2 th", i, j ); LeaveCriticalSection(&cs); } return 0; } int _tmain(int argc, _TCHAR* argv[]) { HANDLE msh[2]; InitializeCriticalSection(&cs); ht1 = CreateThread(NULL, 0, &ThreadProc1, NULL, 0, NULL); ht2 = CreateThread(NULL, 0, &ThreadProc2, NULL, 0, NULL); msh[0] = ht1; msh[1] = ht2; Sleep(500); DeleteCriticalSection(&cs); return 0; } Такой результат я получаю с использованием мьютекса: А вот результат с использованием критической секции:
Ответы
Ответ 1
А с чего им выполняться поочередно? Выделено время до переключения - и пошел-пошел-пошел, сколько успеет... Поток переключился, пошел-пошел-пошел второй... Возможное переключение при Sleep(1) не сработает - оно в критическом разделе. Так что у второго потока шанса запуститься по сути нет... Вот один поток и гонит весь цикл, а потом второй. А мьютекс у вас (код вы не привели, будем ванговать...) пропускает один поток, второй ждет. Мьютекс отпущен, второй тут же включился, первый на своей итерации влетает в ожидание. Второй отпускает - захватывает первый... Ну, и так далее. Без вашего кода трудно точно сказать. А вот если вынести Sleep за пределы раздела - типа for (j=1; j <= 12; j++) { EnterCriticalSection(&cs); i = global; i++; global = i; printf_s( "%4s %4d \n", " 1 th", i ); LeaveCriticalSection(&cs); Sleep (1); } то результат уже не такой грустный :) - 1 th 101 2 th 102 1 1 th 103 2 th 104 2 1 th 105 2 th 106 3 1 th 107 2 th 108 4 1 th 109 2 th 110 5 1 th 111 2 th 112 6 1 th 113 2 th 114 7 1 th 115 2 th 116 8 1 th 117 2 th 118 9 1 th 119 2 th 120 10 1 th 121 2 th 122 11 1 th 123 2 th 124 12 Именно потому, что Sleep обеспечивает переключение на второй поток... Я более-менее доступно пояснил? Только вот учтите, что гарантии поочередного выполнения этим переносом Sleep() не получится - есть теоретическая вероятность, что оба потока уйдут в спячку одновременно... P.S. Убрал Sleep совсем, счетчик поднял до 1000 - получил до 123 первый поток, потом до 1122 второй, потом опять первый... Т.е. переключение есть - но именно такое, как я писал - по времени.Ответ 2
Вся суть и назначение критической секции заключается в том, что захват свободной критической секции - исключительно легкая операция, выполняемая в контексте вызывающего процесса, без системных вызовов и переключения контекста. Попытка же захвата уже занятой критической секции - "тяжелая" операция, которая помещает поток в полноценное состояние системного ожидания - "спячки" (см. однако spin count ниже). По этой причине, если поток, владеющий критической секцией, делает LeaveCriticalSection и EnterCriticalSection без какой-либо паузы между ними, у ждущего ("спящего") потока нет никаких шансов успеть вовремя проснуться и тоже сделать попытку захвата секции. Уже владеющий секцией поток будет все время единолично успешно освобождать и перезахватывать ее. Именно это вы и наблюдаете в вашем эксперименте. Чтобы уравнять шансы потоков, вам надо Либо ввести какую-то задержку между LeaveCriticalSection и EnterCriticalSection, чтобы дать ждущему потоку время проснуться до выполнения EnterCriticalSection. Либо позаботиться о том, чтобы ждущий поток не уходил в состояние системной спячки, пока первый поток "держит" секцию. Этого можно достичь путем указания spin count для критической секции. Разумеется, для такой тяжелой итерации, как у вас, понадобится неразумно большой spin count. Тем не менее, ради эксперимента, в данном примере для облегчения итерации убираем Sleep и делаем InitializeCriticalSectionAndSpinCount(&cs, 10000000); Получаем уже 1 th 101 2 th 102 1 1 th 103 2 th 104 2 2 th 105 3 2 th 106 4 1 th 107 2 th 108 5 2 th 109 6 1 th 110 2 th 111 7 2 th 112 8 1 th 113 1 th 114 2 th 115 9 1 th 116 2 th 117 10 2 th 118 11 2 th 119 12 1 th 120 1 th 121 1 th 122 1 th 123 1 th 124 А если убрать такую "тяжесть", как printf и следить за потоками более эффективными способами, то сработает и существенно меньший spin count.
Комментариев нет:
Отправить комментарий