Консультация № 173316
15.10.2009, 23:16
0.00 руб.
0 8 1
Здравствуйте!Помогите,пожалуйста ,со следующей задачей:
Задача о синхронизации 3-х потоков с помощью флагов:
есть несколько потоков и каждый поток запускается в хаотичном порядке и при этом выдаёт сообщение о том,что запустился.Далее спит,и выдаёт сообщение,что спит.Потом снова запускается.
OS Windows xp,среда разработки Borland c++Builder 6;
Буду очень благодарна,если откликнитесь!

Обсуждение

Неизвестный
16.10.2009, 15:46
общий
Здравствуйте, РАИ!
Из приведенного Вами условия непонятно, что нужно синхронизировать. Синхронизация потоков требуется в случае доступа к общим ресурсам, причем если хотя бы один поток что-то пишет в эти ресурсы. Читать можно и без синхронизации. Единственное, могу предположить, что требуется синхронизировать вывод сообщений в общее окошко (это может быть и консоль), чтобы текст, выдаваемый одним потоком, не прерывался текстом от другого потока. Но в условии не сказано, как потоки сообщают о своем запуске. Это может быть, например, PostMessage, и тогда, опять же, синхронизировать нечего.
Неизвестный
18.10.2009, 14:08
общий
Извините,пожалуйста,за такое неточное условие!Вот более точное:
Есть некоторое число потоков(больше 3).Нужно,чтобы каждый поток при запуске выводиль на консоль сообщение:поток №1 запутился.Потом поток спит несколько секунд с помощью функции Sleep.И завершается, при этом выводит на консоль сообщение ,что он завершился.
Неизвестный
19.10.2009, 17:19
общий
это ответ
Здравствуйте, РАИ.
После Вашего уточнения предлагаю следующую тестовую программку (см. приложение). В программе запускается THREAD_COUNT потоков. Вывод на консоль синхронизируется с помощью критических секций. Дополнительно, главный поток ждет завершения всех порожденных потоков с помощью WaitForMultipleObjects(). Проверял с Visual C++ 6. Программа написана на чистом Win32 API с использованием стандартного вывода на консоль, поэтому должна компилироваться и в среде Borland С++ Builder.

Если что, уточняйте.

Успехов!

Приложение:
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <stdio.h>

// количество запускаемых потоков
#define THREAD_COUNT 5

// критическая секция, которая будет защищать разделяемый ресурс - консоль
CRITICAL_SECTION g_cs;

// Вся синхронизация вывода на консоль заключена в функции PrintMsg()
// Любой участок кода, работающего с разделяемым ресурсом (здесь - консоль),
// нужно заключить в вызовы функций EnterCriticalSection() и LeaveCriticalSection()

void PrintMsg( LPSTR lpszFormat, ... )
{
va_list arg;
va_start( arg, lpszFormat );
EnterCriticalSection( &g_cs );
vprintf( lpszFormat, arg );
LeaveCriticalSection( &g_cs );
va_end( arg );
}

DWORD WINAPI ThreadFunc( PVOID pvParam )
{
int index = (int)pvParam;
PrintMsg( "Thread %d started.\n" , index );
Sleep( 5000 );
PrintMsg( "Thread %d finished.\n" , index );
return 0;
}


int main( int argc, char* argv[] )
{
HANDLE* phThreads = new HANDLE[THREAD_COUNT];
if( !phThreads ) return -1;

// критическую секцию следует инициализировать до обращения
// какого-либо потока к защищенному ресурсу
InitializeCriticalSection( &g_cs );

// iCounter - счетчик числа потоков
for( int iCounter = 0; iCounter < THREAD_COUNT; ) {
// создаем новый поток, номер потока передаем как параметр
++iCounter;
DWORD dwThreadId;
phThreads[iCounter-1] = CreateThread( NULL, 0, ThreadFunc,
(PVOID)iCounter, 0, &dwThreadId );
}
// подождем, пока все потоки завершатся
WaitForMultipleObjects( iCounter, phThreads, TRUE, THREAD_COUNT*5000 );

// закрываем описатели потоков
// В данном случае это делать необязательно - описатели потоков
// будут закрыты при завершении процесса
while( iCounter > 0 )
CloseHandle( phThreads[--iCounter] );

return 0;
}
Неизвестный
19.10.2009, 17:27
общий
РАИ:
Извините, отправил ответ, и заметил, что забыл удалить критическую секцию:
после WaitForMultipleObjects() надо добавить

DeleteCriticalSection( &g_cs );

И еще (вот ведь растяпа!) надо удалить phThreads:

delete[] phThreads;

В данном примере это несущественно (при выходе все освобождается), но в серьезной программе забывать про освобождение ресурсов нельзя.

Так что конец будет выглядеть как:

Код:
	// подождем, пока все потоки завершатся
WaitForMultipleObjects( iCounter, phThreads, TRUE, THREAD_COUNT*5000 );
DeleteCriticalSection( &g_cs );

// закрываем описатели потоков
// В данном случае это делать необязательно - описатели потоков
// будут закрыты при завершении процесса
while( iCounter > 0 )
CloseHandle( phThreads[--iCounter] );

delete[] phThreads;
return 0;
}
Неизвестный
19.10.2009, 20:17
общий
Спасибо большое!Только не могли бы вы прокоментировать построчно для лучшего понимания ,а то компилятору не нравится инициализация критич.секции,выдаёт ошибки типа
"Type mismach in redeclaration of '__stdcall InitializeCriticalSection(_RTL_CRITICAL_SECTION*);'
Неизвестный
20.10.2009, 18:23
общий
А на какую именно строку выдает эту ошибку? Дело в том, что собственно в тексте программы нет повторного объявления (redeclaration) функции InitializeCriticalSection (только в стандартном заголовочном файле).

Добавил комментарии на основе книги
Рихтер Дж.
"Windows для профессионалов: создание эффективных Win32 приложений с
учетом специфики 64-разрядной версии Windows"

Саму эту книгу на русском языке можно найти в интернете в CHM формате. Выполните поиск richter.chm и сразу получите ссылку, где ее взять (проверил).

Код:

#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <stdio.h>

// количество запускаемых потоков
#define THREAD_COUNT 5

// критическая секция (управляющая структура), которая будет защищать разделяемый ресурс - консоль
CRITICAL_SECTION g_cs;

/* БОльшая часть комментариев - цитаты из книги
Рихтер Дж.
"Windows для профессионалов: создание эффективных Win32 приложений с
учетом специфики 64-разрядной версии Windows"

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

Структура CRITICAL_SECTION содержит данные, необходимые для реализации
подобного раздельного доступа. Ее внутренняя организация несущественна для
использования. Прикладные программы не должны обращаться к полям этой
структуры. Вы работаете со структурой CRITICAL_SECTION исключительно
через функции Windows, передавая им адрес соответствующего экземпляра
этой структуры. Функции сами знают, как обращаться с её элементами, и
гарантируют, что она всегда будет в согласованном состоянии.
*/


/*
Вся синхронизация вывода на консоль заключена в функции PrintMsg()

Любой участок кода, работающего с разделяемым ресурсом (здесь - консоль),
нужно заключить в вызовы функций EnterCriticalSection() и LeaveCriticalSection()

Участок кода, работающий с разделяемым ресурсом, предваряется вызовом
EnterCriticalSection()

- Если ресурс свободен, EnterCriticalSection модифицирует элементы
структуры, указывая, что вызывающий поток занимает ресурс, после чего
немедленно возвращает управление, и поток продолжает свою работу
(получив доступ к ресурсу).

- Если значения элементов структуры свидетельствуют, что ресурс уже
захвачен вызывающим потоком, EnterCriticalSection обновляет их,
отмечая тем самым, сколько раз подряд этот поток захватил ресурс, и
немедленно возвращает управление. Такая ситуация бывает нечасто — лишь
тогда, когда поток два раза подряд вызывает EnterCriticalSection без
промежуточного вызова LeaveCriticalSection.

- Если значения элементов структуры указывают на то, что ресурс занят
другим потоком, EnterCriticalSection переводит вызывающий поток в
режим ожидания. Поток, пребывая в ожидании, не тратит ни кванта
процессорного времени. Система запоминает, что данный поток хочет
получить доступ к ресурсу, и - как только поток, занимавший этот
ресурс, вызывает LeaveCriticalSection — вновь начинает выделять нашему
потоку процессорное время. При этом она передает ему ресурс,
автоматически обновляя элементы структуры CRITICAL_SECTION.

В конце участка кода, использующего разделяемый ресурс, должен
присутствовать вызов LeaveCriticalSection.

Эта функция просматривает элементы структуры CRITICAL_SECTION и
уменьшает счетчик числа захватов ресурса вызывающим потоком на 1. Если
его значение больше 0, LeaveCriticalSection ничего не делает и просто
возвращает управление.

Если значение счетчика достигло 0, LeaveCriticalSection сначала
выясняет, есть ли в системе другие потоки, ждущие данный ресурс в
вызове EnterCriticalSection. Если есть хотя бы один такой поток,
функция настраивает значения элементов структуры, что бы они
сигнализировали о занятости ресурса, и отдает его одному из ждущих
потоков (поток выбирается "по справедливости"). Если же ресурс никому
не нужен, Leave CriticalSection соответственно сбрасывает элементы
структуры.

Как и EnterCriticalSection, функция LeaveCriticalSection выполняет все
действия на уровне атомарного доступа. Однако LeaveCriticalSection
никогда не приостанавливает поток, а управление возвращает немедленно.
*/

void PrintMsg( LPSTR lpszFormat, ... )
{
va_list arg;
va_start( arg, lpszFormat );
EnterCriticalSection( &g_cs );
vprintf( lpszFormat, arg );
LeaveCriticalSection( &g_cs );
va_end( arg );
}

DWORD WINAPI ThreadFunc( PVOID pvParam )
{
int index = (int)pvParam;
PrintMsg( "Thread %d started.\n" , index );
Sleep( 5000 );
PrintMsg( "Thread %d finished.\n" , index );
return 0;
}


int main( int /*argc*/, char* /*argv[]*/ )
{
// выделяем память под массив для описателей порожденных потоков
HANDLE* phThreads = new HANDLE[THREAD_COUNT];
if( !phThreads ) return -1;

// критическую секцию следует инициализировать до обращения
// какого-либо потока к защищенному ресурсу
InitializeCriticalSection( &g_cs );

// iCounter - счетчик числа потоков
for( int iCounter = 0; iCounter < THREAD_COUNT; ) {
// создаем новый поток, номер потока передаем как параметр
++iCounter;
DWORD dwThreadId;
// сохраняем описатель, поскольку нам надо дождаться завершения всех
// порожденных потоков
phThreads[iCounter-1] = CreateThread( NULL, 0, ThreadFunc,
(PVOID)iCounter, 0, &dwThreadId );
}
// подождем, пока все потоки завершатся
WaitForMultipleObjects( iCounter, phThreads, TRUE, THREAD_COUNT*5000 );

// критическая секция больше не нужна, освобождаем ресурс
DeleteCriticalSection( &g_cs );

// закрываем описатели потоков
// В данном случае это делать необязательно - описатели потоков
// будут закрыты при завершении процесса
while( iCounter > 0 )
CloseHandle( phThreads[--iCounter] );

// освобождаем память
// В данном случае это делать необязательно - память будет освобождена
// при завершении процесса
delete[] phThreads;
return 0;
}

Неизвестный
20.10.2009, 18:42
общий
Ошибку выдаёт в строке инициализации критической секции(перед параметром &g_cs),кроме того есть ещё следующие ошибки:
в той же строке 'Illegal initialization' и 'type name expected'.Можете подсказать,в чём причина?Может быть дело в среде разработки?
Огромное спасибо за комментарии!!!
Неизвестный
20.10.2009, 18:56
общий
Всё исправлено!Спасибо за помощь,amnick!!!
Форма ответа