Есть у меня потоковая функция ( которую я вызываю через _beginthreadex ):
unsigned int __stdcall MyThreadFunc(void* pParam)
{
int i;
MyStruct *pMySTruct = (MyStruct*)pParam;
volatile int *threadflag = &(pMyStruct->threadflag);
int numworkmode = pMyStruct->numworkmode;
CInitConds ic = pMyStruct->ic;
int iterQnty = pMyStruct->iterQnty;
double sgm = pMyStruct->sgm;
double *dlt = pMyStruct->dlt;
CMyPage *pMyPage = pMyStruct->pMyPage;
bool *pisthreadfinished = &(pMyStruct->isthreadfinished);
pMyPage->Calculate(*(pMyPage->m_pIc),&iterQnty,&sgm,&dlt[0],numworkmode,threadflag); // вызов функции, которая производит математические вычисления
pMyStruct->iterQnty = iterQnty;
pMyStruct->sgm = sgm;
pMyStruct->ic = *(pMyPage->m_pIc);
memcpy(pMyStruct->dlt,dlt,10*sizeof(double));
*pisthreadfinished = true;
return 0;
}
внутри функции CMyPage::Calculate я написал следующий код на случай приостановки работы потока:
if (*threadflag==2)
// если поток был приостановлен
{
while ((*threadflag!=0)&&(*threadflag!=1))
// пока не была дана команда выйти из потока (*threadflag==0) или возобновить работу потока (*threadflag==1)
{
Sleep(1000); // ожидание в течение одной секундыif (*threadflag==0)
// если была дана команда выйти из потока
{
FreeMemory(); // освобождение ресурсовreturn;
}
}
}
Вопрос состоит в следующем--правильно ли я делаю, что организовал цикл while ((*threadflag!=0)&&(*threadflag!=1)) и в нём вызываю команду Sleep(1000); для ожидания команды завершить или возобновить поток? Или же можно как-то по-другому это сделать?
Здравствуйте, RussianFellow, Вы писали:
RF>Вопрос состоит в следующем--правильно ли я делаю, что организовал цикл while ((*threadflag!=0)&&(*threadflag!=1)) и в нём вызываю команду Sleep(1000); для ожидания команды завершить или возобновить поток? Или же можно как-то по-другому это сделать?
Ну, совсем неправильно было бы НЕ ВЫЗЫВАТЬ Sleep, это вообще busy wait был бы -- совсем антипаттерн.
Так -- полумера. Код, который может работать иногда вполне нормально, но тоже антипаттерн.
Правильно -- использовать event -ы, если в win , то есть напрямую такие объекты ядра, а если cross platform -- то в новой С++ или в Boost можно найти
нужные примитивы.
Основная идея в том, что Sleep пустого не дожно быть, ты должен ждать наступления какого-то события, сигнала, который тебе
пошлют другие потоки. МОжно через event-ы, можно через другие средства IPC, типа ввода-вывода, но обязательно ожидать чего-то,
после чего можно будет продолжить работу сражу, а не циклиться в пустом цикле.
Твой код например обладает таким недостатком, что если поток только что ушёл в Sleep, а ты ему "послал какой-то сигнал" в виде
установки соотв. переменной, то он "увидит" этот сигнал не ранее, чем через 1 сек. в твоём случае (1000 милисек).
Если потоков будет много (N), и все нужно останавливать, то это может вылиться в до N*1000 милисекунд ожидания.
Ну и просто так переменные, разделяемые между потоками, проверять нельзя, нужно либо защищать их мьютексами
на время проверки, либо использовать std::atomic. Это может работать, но может и не работать. Тут это -- меньшее из
твоих двух зол.
Здравствуйте, RussianFellow, Вы писали:
RF>Вопрос состоит в следующем--правильно ли я делаю, что организовал цикл while ((*threadflag!=0)&&(*threadflag!=1)) и в нём вызываю команду Sleep(1000); для ожидания команды завершить или возобновить поток? Или же можно как-то по-другому это сделать?
1. Делать принудительную самоостановку — Sleep(t) — плохая практика, ибо это приводит к тормозам в другом потоке, который захочет повзаимодействовать с твоим (например, на предмет завершения программы).
Тем более, засыпать с темпом 1 секунда.
2. Вместо активного опроса — while(!condition()) sleep(timeout); — который для маленьких таймаутов приводит к перегреву, а для больших — к тормозам, — следует использвовать условные переменные.
std::mutex guard; // защищает данные, участвующие в вычислении условия
std::condition_variable cv; // следит за изменением данныхenum class Flag { Run, Suspend, Exit };
Flag flag; // эти самые данные
// пример функции слеженияbool wait_after_suspension() {
std::unique_lock<std::mutex> lock(guard);
cv.wait(lock, []() { return flag != Flag::Suspend; });
return flag != Flag::Exit;
}
// пример функции установкиvoid set_flag(Flag value) {
std::unique_lock<std::mutex> lock(guard);
if (flag != v) {
flag = v;
cv.notify_all();
}
}
Ну или что-то в таком роде.
На голом винапи условные переменные появились, если мне не изменяет память, начиная с висты. Для совместимости с более ранними версиями винды можно устроить велосипеды на эвентах.
Но проще взять
#include <thread>
и пусть у компиляторщиков болит голова о правильной безглючной реализации имеющимися средствами.
3. Чтобы два раза не вставать. Ни в коем случае не пытайся тормозить поток извне, с помощью ::SuspendThread. Это очень короткая дорога к дедлокам.
void victim_thread() {
void* p = malloc(1); // 1
free(p);
}
HANDLE hVictim;
void another_thread() {
void* p = malloc(1); // 2
free(p); // 3
}
void killer_thread() {
SuspendThread(hVictim); // в тот момент, когда он был внутри malloc и успел захватить мьютекс менеджера кучи
// another_thread заходит в 2 или в 3 и засыпает там в очереди перед заблокированным мьютексомvoid* p = malloc(1); // убийца тоже заходит внутрь и засыпает перед заблокированным мьютексом
free(p);
ResumeThread(hVictim); // сюда мы никогда не попадём
}
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, RussianFellow, Вы писали:
RF>>Вопрос состоит в следующем--правильно ли я делаю, что организовал цикл while ((*threadflag!=0)&&(*threadflag!=1)) и в нём вызываю команду Sleep(1000); для ожидания команды завершить или возобновить поток? Или же можно как-то по-другому это сделать?
К>1. Делать принудительную самоостановку — Sleep(t) — плохая практика, ибо это приводит к тормозам в другом потоке, который захочет повзаимодействовать с твоим (например, на предмет завершения программы).
Некоторые так баги фиксят... У нас на проекте вскрылась проблема start-start что привело к отваливанию всего приложения. Угадай, как пофиксили? Sleep(300) . И не то, чтобы кто-то не умел фиксить по другому, просто так было проще (моё мнение — нафиг такое проще, но продукт работает и время не ждёт ).
Здравствуйте, Кодт, Вы писали:
К>На голом винапи условные переменные появились, если мне не изменяет память, начиная с висты. Для совместимости с более ранними версиями винды можно устроить велосипеды на эвентах.
А как, интересно, работать с эвентами (событиями)? Стоит ли использовать MFC-шный класс CEvent или стоит использовать другие средства для работы с эвентами?
Не могли бы Вы, Кодт, привести пример работы с эвентами?
Здравствуйте, Kernan, Вы писали:
К>>1. Делать принудительную самоостановку — Sleep(t) — плохая практика, ибо это приводит к тормозам в другом потоке, который захочет повзаимодействовать с твоим (например, на предмет завершения программы). K>Некоторые так баги фиксят... У нас на проекте вскрылась проблема start-start что привело к отваливанию всего приложения. Угадай, как пофиксили? Sleep(300) . И не то, чтобы так кто-то не умел фиксить по другому, просто так было проще (моё мненеи — нафиг такое проще).
Здравствуйте, RussianFellow, Вы писали:
RF>Здравствуйте, Кодт, Вы писали:
К>>На голом винапи условные переменные появились, если мне не изменяет память, начиная с висты. Для совместимости с более ранними версиями винды можно устроить велосипеды на эвентах.
RF>А как, интересно, работать с эвентами (событиями)? Стоит ли использовать MFC-шный класс CEvent или стоит использовать другие средства для работы с эвентами?
RF>Не могли бы Вы, Кодт, привести пример работы с эвентами?
Почитай Джеффри Рихтера что ли и книку Concurrency In Action (есть перевод и есть даже в сети).
Здравствуйте, RussianFellow, Вы писали:
RF>А как, интересно, работать с эвентами (событиями)? Стоит ли использовать MFC-шный класс CEvent или стоит использовать другие средства для работы с эвентами?
CEvent — это всего лишь обёртка над хэндлом. Конечно, лучше пользоваться им, чтобы избавить себя от рутины по созданию-убиванию системных объектов.
Как пользоваться:
CEvent please_exit(false, true); // исходно сброшено, автоматически не сбросится по прочтению
CEvent please_run(true, true); // исходно взведено, автоматически не сбросится (нужен явный Reset)bool wait() {
HANDLE objects[2] = { (HANDLE)please_exit, (HANDLE)please_run }; // более приоритетные события упоминаем первымиint result = WaitForMultipleObjects(2, &objects, false, INFINITE);
if (result == WAIT_OBJECT_0) return false; // please_exit взведёнif (result == WAIT_OBJECT_0 + 1) return true; // please_run взведён
// WAIT_TIMEOUT мы здесь не ожидаем, поэтому прикручиваем обработку ошибок и сваливаем!return false;
}
void trigger_exit() {
please_exit.SetEvent();
}
void allow_run(bool yes) {
if (yes)
please_run.SetEvent();
else
please_run.ResetEvent();
}
Здравствуйте, Kernan, Вы писали:
K>Некоторые так баги фиксят... У нас на проекте вскрылась проблема start-start что привело к отваливанию всего приложения. Угадай, как пофиксили? Sleep(300) . И не то, чтобы кто-то не умел фиксить по другому, просто так было проще (моё мнение — нафиг такое проще, но продукт работает и время не ждёт ).
Так борются с промежуточным состоянием мультивибратора: добавляют RC-цепочку, чтобы при включении гарантированно прижать состояние к 0 или 1.
Разрулить гонку старт-старт фиксированной задержкой — хорошо, но ровно до тех пор, пока не попадётся особо быстрое / медленное / вываливающееся из свопа окружение, которое оттормозит первый поток на больше чем 300 мс.
HANDLE CreateEvent
(
LPSECURITY_ATTRIBUTES lpEventAttributes, // атрибут защиты
BOOL bManualReset, // тип сброса TRUE - ручной
BOOL bInitialState, // начальное состояние TRUE - сигнальное
LPCTSTR lpName // имя обьекта
);
Если функция успешна, то вернется дескриптор события. В том случае, если с таким именем событие уже создано, то вернется дескриптор уже созданного события, а GetLastError() вернет код ERROR_ALREADY_EXISTS. Но функция может вернуть и NULL, если объект события создать не удалось совсем. Функция SetEvent() меняет состояние на сигнальное (есть событие).
BOOL SetEvent
(
HANDLE hEvent // дескриптор события
);
В случае успеха вернет ненулевое значение. А функция ResetEvent() меняет состояние на невыделенное (нет события):
BOOL ResetEvent
(
HANDLE hEvent // дескриптор события
);
Создает объект Event, запускает три потока. Каждый поток ждет когда объект синхронизации перейдет в сигнальное состояние. После некоторой задержки устанавливаем его в сигнальное состояние SetEvent(), выжидаем некоторое время, чтобы потоки среагировали и сбрасываем ResetEvent(). Обратите внимание, что в данном случае в отличии от всех других объектов синхронизации потоков может быть любое количество.
То есть я задал flag = 2 для приостановки работы потока, а в потоковой функции я написал
if (*threadflag==2)
// если поток был приостановлен
{
DWORD dwWairResult;
while (dwWaitResult!=WAIT_OBJECT_0)
// ждём, пока не появится событие (по которому надо или продолжить работу потока, или выйти из него)
{
dwWaitResult = WaitForSIngleObject(event,1);
}
if (*threadflag==0)
{
FreeMemory(); // освобождение ресурсовreturn; // выход из функции потока
}
// если *threadflag==1 , то продолжаем работу потока
}
Здравствуйте, RussianFellow, Вы писали:
RF>Здравствуйте, Muxa, Вы писали:
RF>>>Или же можно как-то по-другому это сделать?
M>>Можно, но не делай так как тебе советуют выше.
RF>Почему?
Потому что надо внимательно читать книгу Рихтера — там все есть и подробно рассказано.
Здравствуйте, MasterZiv, Вы писали:
MZ>Твой код например обладает таким недостатком, что если поток только что ушёл в Sleep, а ты ему "послал какой-то сигнал" в виде MZ>установки соотв. переменной, то он "увидит" этот сигнал не ранее, чем через 1 сек. в твоём случае (1000 милисек). MZ>Если потоков будет много (N), и все нужно останавливать, то это может вылиться в до N*1000 милисекунд ожидания.
Почему это? Выставили все флаги сразу, и отсчет времени пошел. Вот если сигналы нужно посылать именно последовательно...
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, RussianFellow, Вы писали:
RF>Но это был пример использования событий при помощи MFC. А как без MFC?
Ещё раз: MFC — это предельно примитивная обёртка над голым WinAPI.
Конструктор CEvent — просто берёт и вызывает CreateEvent(NULL, bManualReset, bInitialState, NULL).
В моём примере — у события "флаг выхода" установлено (false,false) — т.е. "ещё не надо выходить", а у "флаг продолжения" — (false,true) — т.е. "уже можно работать".
Когда управляющий поток сочтёт, что управляемый должен заснуть, — он сделает ResetEvent и опустит семафор. Когда сочтёт, что должен работать — поднимет. Когда пора будет завершаться — поднимет другой семафор.
RF>На www.firststeps.ru я нашёл пример:
Можно было не цитировать сюда перепечатку с MSDN.
RF>Пример смотрим. RF>[/q] RF>
RF>void Test1(void *)
RF>{
// ПЛОХАЯ КИСА! Неинициализированная переменная!
RF> DWORD dwWaitResult;
RF> while(dwWaitResult!=WAIT_OBJECT_0) {
RF> dwWaitResult = WaitForSingleObject(event,1);
// Сфига ли таймаут, если могли дождаться?
RF> cout << "Test 1 TIMEOUT" << endl;
RF> }
RF> cout << "Event Test 1 " << endl;
// ПЛОХАЯ КИСА! Самоубийство! Поток, запущенный по beginthread{ex}, корректно завершается по выходу из основной функции.
RF> _endthread();
RF>}
RF>
RF>[/q]
RF>То есть я задал flag = 2 для приостановки работы потока, а в потоковой функции я написал
RF>то правильно ли я сделал?
Разумеется, неправильно. Потому что скопировал говнокод с firststeps.ru.
Здравствуйте, Кодт, Вы писали:
К>1. Делать принудительную самоостановку — Sleep(t) — плохая практика
Николай, да ты совершенно правильно сказал, лучше так не делать, но все же если нет гербовой то пишем на простой — Sleep делать можно, но только надо указывать время сна — 1 или даже 0. По сути это просто отдать свой квант если делать сейчас нечего.