Как правильно приостановить поток?
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 30.01.17 09:43
Оценка:
Есть у меня потоковая функция ( которую я вызываю через _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); для ожидания команды завершить или возобновить поток? Или же можно как-то по-другому это сделать?
1613 г. = 2024 г.
Re: Как правильно приостановить поток?
От: MasterZiv СССР  
Дата: 30.01.17 10:03
Оценка: +1
Здравствуйте, 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. Это может работать, но может и не работать. Тут это -- меньшее из
твоих двух зол.
Re: Как правильно приостановить поток?
От: Кодт Россия  
Дата: 30.01.17 10:12
Оценка: 8 (2)
Здравствуйте, 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); // сюда мы никогда не попадём
}
Перекуём баги на фичи!
Re[2]: Как правильно приостановить поток?
От: Kernan Ниоткуда https://rsdn.ru/forum/flame.politics/
Дата: 30.01.17 11:07
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, RussianFellow, Вы писали:


RF>>Вопрос состоит в следующем--правильно ли я делаю, что организовал цикл while ((*threadflag!=0)&&(*threadflag!=1)) и в нём вызываю команду Sleep(1000); для ожидания команды завершить или возобновить поток? Или же можно как-то по-другому это сделать?


К>1. Делать принудительную самоостановку — Sleep(t) — плохая практика, ибо это приводит к тормозам в другом потоке, который захочет повзаимодействовать с твоим (например, на предмет завершения программы).

Некоторые так баги фиксят... У нас на проекте вскрылась проблема start-start что привело к отваливанию всего приложения. Угадай, как пофиксили? Sleep(300) . И не то, чтобы кто-то не умел фиксить по другому, просто так было проще (моё мнение — нафиг такое проще, но продукт работает и время не ждёт ).
Sic luceat lux!
Отредактировано 30.01.2017 12:28 Kernan . Предыдущая версия . Еще …
Отредактировано 30.01.2017 12:27 Kernan . Предыдущая версия .
Re[2]: Как правильно приостановить поток?
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 30.01.17 11:35
Оценка:
Здравствуйте, Кодт, Вы писали:

К>На голом винапи условные переменные появились, если мне не изменяет память, начиная с висты. Для совместимости с более ранними версиями винды можно устроить велосипеды на эвентах.


А как, интересно, работать с эвентами (событиями)? Стоит ли использовать MFC-шный класс CEvent или стоит использовать другие средства для работы с эвентами?

Не могли бы Вы, Кодт, привести пример работы с эвентами?
1613 г. = 2024 г.
Re[3]: Как правильно приостановить поток?
От: Слава  
Дата: 30.01.17 11:48
Оценка:
Здравствуйте, Kernan, Вы писали:

К>>1. Делать принудительную самоостановку — Sleep(t) — плохая практика, ибо это приводит к тормозам в другом потоке, который захочет повзаимодействовать с твоим (например, на предмет завершения программы).

K>Некоторые так баги фиксят... У нас на проекте вскрылась проблема start-start что привело к отваливанию всего приложения. Угадай, как пофиксили? Sleep(300) . И не то, чтобы так кто-то не умел фиксить по другому, просто так было проще (моё мненеи — нафиг такое проще).

Ну например в треде http://rsdn.org/forum/job/6681681.1
Автор: IT
Дата: 30.01.17
пропагандируют этакое скоростное забивание болтов кувалдой. Заказчик-де требует.
Re[4]: Как правильно приостановить поток?
От: Kernan Ниоткуда https://rsdn.ru/forum/flame.politics/
Дата: 30.01.17 12:24
Оценка:
Здравствуйте, Слава, Вы писали:

С>Ну например в треде http://rsdn.org/forum/job/6681681.1
Автор: IT
Дата: 30.01.17
пропагандируют этакое скоростное забивание болтов кувалдой. Заказчик-де требует.

Ага, видео в тему
Sic luceat lux!
Отредактировано 30.01.2017 12:26 Kernan . Предыдущая версия .
Re[3]: Как правильно приостановить поток?
От: Kernan Ниоткуда https://rsdn.ru/forum/flame.politics/
Дата: 30.01.17 12:29
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>Здравствуйте, Кодт, Вы писали:


К>>На голом винапи условные переменные появились, если мне не изменяет память, начиная с висты. Для совместимости с более ранними версиями винды можно устроить велосипеды на эвентах.


RF>А как, интересно, работать с эвентами (событиями)? Стоит ли использовать MFC-шный класс CEvent или стоит использовать другие средства для работы с эвентами?


RF>Не могли бы Вы, Кодт, привести пример работы с эвентами?

Почитай Джеффри Рихтера что ли и книку Concurrency In Action (есть перевод и есть даже в сети).
Sic luceat lux!
Re[3]: Как правильно приостановить поток?
От: Кодт Россия  
Дата: 30.01.17 14:26
Оценка:
Здравствуйте, 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();
}

Это в самом тупом виде.
Перекуём баги на фичи!
Re[3]: Как правильно приостановить поток?
От: Кодт Россия  
Дата: 30.01.17 14:34
Оценка:
Здравствуйте, Kernan, Вы писали:

K>Некоторые так баги фиксят... У нас на проекте вскрылась проблема start-start что привело к отваливанию всего приложения. Угадай, как пофиксили? Sleep(300) . И не то, чтобы кто-то не умел фиксить по другому, просто так было проще (моё мнение — нафиг такое проще, но продукт работает и время не ждёт ).


Так борются с промежуточным состоянием мультивибратора: добавляют RC-цепочку, чтобы при включении гарантированно прижать состояние к 0 или 1.

Разрулить гонку старт-старт фиксированной задержкой — хорошо, но ровно до тех пор, пока не попадётся особо быстрое / медленное / вываливающееся из свопа окружение, которое оттормозит первый поток на больше чем 300 мс.
Перекуём баги на фичи!
Re: Как правильно приостановить поток?
От: Muxa  
Дата: 30.01.17 22:52
Оценка: +1
RF>Или же можно как-то по-другому это сделать?

Можно, но не делай так как тебе советуют выше.
В коде, что у тебя сейчас есть, ты хотя бы разбираешься и можешь его развивать и поддерживать.
Re[2]: Как правильно приостановить поток?
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 31.01.17 06:52
Оценка:
Здравствуйте, Muxa, Вы писали:

RF>>Или же можно как-то по-другому это сделать?


M>Можно, но не делай так как тебе советуют выше.


Почему?
1613 г. = 2024 г.
Re[4]: Как правильно приостановить поток?
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 31.01.17 07:03
Оценка:
Ясно, спасибо!

Но это был пример использования событий при помощи MFC. А как без MFC?

На www.firststeps.ru я нашёл пример:

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        // дескриптор события
);

Пример смотрим.


// TestEvent.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"
#include "iostream.h"
#include "process.h" 

HANDLE event;

void Test1(void *);
void Test2(void *);
void Test3(void *);

void main()
{
    event=CreateEvent(NULL,TRUE,FALSE,"FirstStep");
    if (_beginthread(Test1,1024,NULL)==-1)
        cout << "Error begin thread " << endl;
    if (_beginthread(Test2,1024,NULL)==-1)
        cout << "Error begin thread " << endl;
    if (_beginthread(Test3,1024,NULL)==-1)
        cout << "Error begin thread " << endl;
    if (event!=NULL){
        Sleep(1000);
        SetEvent(event);
        Sleep(1000);
        ResetEvent(event);
        CloseHandle(event);
    } else {
        cout << "error create event" << endl;
    }

}

void Test1(void *)
{
    DWORD dwWaitResult;
    while(dwWaitResult!=WAIT_OBJECT_0) {
        dwWaitResult = WaitForSingleObject(event,1);
        cout << "Test 1 TIMEOUT" << endl;
    }
    cout << "Event Test 1 " << endl;
    _endthread();
}

void Test2(void *)
{
    DWORD dwWaitResult;
    while(dwWaitResult!=WAIT_OBJECT_0){
        dwWaitResult = WaitForSingleObject(event,1);
        cout << "Test 2 TIMEOUT" << endl;
    }
    cout << "Event Test 2 " << endl;
    _endthread();
}

void Test3(void *)
{
    DWORD dwWaitResult;
    while(dwWaitResult!=WAIT_OBJECT_0){
        dwWaitResult = WaitForSingleObject(event,1);
        cout << "Test 3 TIMEOUT" << endl;
    }
    cout << "Event Test 3 " << endl;
    _endthread();
}

Создает объект 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 , то продолжаем работу потока
}


то правильно ли я сделал?
1613 г. = 2024 г.
Отредактировано 31.01.2017 7:04 RussianFellow . Предыдущая версия .
Re[3]: Как правильно приостановить поток?
От: Nikita123 Россия  
Дата: 31.01.17 08:32
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>Здравствуйте, Muxa, Вы писали:


RF>>>Или же можно как-то по-другому это сделать?


M>>Можно, но не делай так как тебе советуют выше.


RF>Почему?

Потому что надо внимательно читать книгу Рихтера — там все есть и подробно рассказано.
Желаю успеха,
Никита.
Re[2]: Как правильно приостановить поток?
От: Ops Россия  
Дата: 31.01.17 08:37
Оценка:
Здравствуйте, MasterZiv, Вы писали:

MZ>Твой код например обладает таким недостатком, что если поток только что ушёл в Sleep, а ты ему "послал какой-то сигнал" в виде

MZ>установки соотв. переменной, то он "увидит" этот сигнал не ранее, чем через 1 сек. в твоём случае (1000 милисек).
MZ>Если потоков будет много (N), и все нужно останавливать, то это может вылиться в до N*1000 милисекунд ожидания.

Почему это? Выставили все флаги сразу, и отсчет времени пошел. Вот если сигналы нужно посылать именно последовательно...
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[5]: Как правильно приостановить поток?
От: Кодт Россия  
Дата: 31.01.17 10:41
Оценка:
Здравствуйте, 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.
Перекуём баги на фичи!
Re[3]: Как правильно приостановить поток?
От: Muxa  
Дата: 31.01.17 10:55
Оценка:
RF>Почему?
Потому что дочитай сообщение до конца.
Re[6]: Как правильно приостановить поток?
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 31.01.17 11:47
Оценка:
Здравствуйте, Кодт, Вы писали:

RF>>то правильно ли я сделал?


К>Разумеется, неправильно. Потому что скопировал говнокод с firststeps.ru.


А как сделать правильно при помощи WinAPI (без MFC)? То есть при помощи CreateEvent, SetEvent, ResetEvent?
1613 г. = 2024 г.
Re[2]: Как правильно приостановить поток?
От: Andrew.W Worobow https://github.com/Worobow
Дата: 31.01.17 12:31
Оценка:
Здравствуйте, Кодт, Вы писали:

К>1. Делать принудительную самоостановку — Sleep(t) — плохая практика


Николай, да ты совершенно правильно сказал, лучше так не делать, но все же если нет гербовой то пишем на простой — Sleep делать можно, но только надо указывать время сна — 1 или даже 0. По сути это просто отдать свой квант если делать сейчас нечего.
Не все кто уехал, предал Россию.
Re: Как правильно приостановить поток?
От: Философ Ад http://vk.com/id10256428
Дата: 31.01.17 12:46
Оценка:
Здравствуйте, RussianFellow, Вы писали:

Так можно — это посредственное решение, но Event'ы использовать лучше — это хорошее решение. В общем, юзай AutoResetEvent'ы
Всё сказанное выше — личное мнение, если не указано обратное.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.