Re[3]: Как грамотно уничтожить поток в MFC
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 27.12.16 13:30
Оценка:
Вот у меня такая ситуация: есть функция потока MyThreadFunc, которой передаются необходимые параметры в переменной типа LPVOID. Функция MyThreadFunc переводит данные из LPVOID в структуру, из которой она передаёт данные из этой структуры в виде параметров функции MyFunc2 (эта функция не является членом класса). Функция MyFunc2 сначала создаёт объект obj класса CMyClass, а потом вызывает функцию obj.Calculate класса CMyCLass, в которой и производятся математические вычисления.

Как мне лучше в этом случае завершать выполнение потока, которому соответствует функция MyTreadFunc? Вызывать функцию TerminateThread из внешней функции (то есть оттуда, откуда был вызван этот поток), использовать AfxEndThread или ExitThread внутри функции MyThreadFunc, посылать сообщение из внешней функции этому потоку, а потом его обрабатывать, изменить код функции CMyClass::Calculate , чтобы был возможен выход из неё по получению некоего сообщения или какому-то другому признаку?
1613 г. = 2024 г.
Re[4]: Как грамотно уничтожить поток в MFC
От: Evgeniy Skvortsov Россия  
Дата: 27.12.16 13:46
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>Вот у меня такая ситуация: есть функция потока MyThreadFunc, которой передаются необходимые параметры в переменной типа LPVOID.

Какой функцией запускается поток?
Re[5]: Как грамотно уничтожить поток в MFC
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 27.12.16 13:50
Оценка:
Здравствуйте, Evgeniy Skvortsov, Вы писали:

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


RF>>Вот у меня такая ситуация: есть функция потока MyThreadFunc, которой передаются необходимые параметры в переменной типа LPVOID.

ES>Какой функцией запускается поток?

OnButton1Click
1613 г. = 2024 г.
Re[6]: Как грамотно уничтожить поток в MFC
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 27.12.16 13:52
Оценка:
Здравствуйте, RussianFellow, Вы писали:

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


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


RF>>>Вот у меня такая ситуация: есть функция потока MyThreadFunc, которой передаются необходимые параметры в переменной типа LPVOID.

ES>>Какой функцией запускается поток?

RF>OnButton1Click


Ой, извиняюсь, неправильно понял.
Поток запускается, скажем, функцией AfxBeginThread.
1613 г. = 2024 г.
Re[6]: Как грамотно уничтожить поток в MFC
От: Evgeniy Skvortsov Россия  
Дата: 27.12.16 13:53
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>OnButton1Click


Нет, я имел ввиду какая функция используется для создания потока? AfxBeginThread, CreateThread или _beginthread ?
Re[7]: Как грамотно уничтожить поток в MFC
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 27.12.16 13:53
Оценка:
Здравствуйте, Evgeniy Skvortsov, Вы писали:

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


RF>>OnButton1Click


ES>Нет, я имел ввиду какая функция используется для создания потока? AfxBeginThread, CreateThread или _beginthread ?


AfxBeginThread
1613 г. = 2024 г.
Re[8]: Как грамотно уничтожить поток в MFC
От: Evgeniy Skvortsov Россия  
Дата: 27.12.16 13:58
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>AfxBeginThread


Нормальное завершение потока — просто выйти из функции, то есть
return 0;
Re[9]: Как грамотно уничтожить поток в MFC
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 27.12.16 14:05
Оценка:
Здравствуйте, Evgeniy Skvortsov, Вы писали:

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


RF>>AfxBeginThread


ES>Нормальное завершение потока — просто выйти из функции, то есть

ES>
ES>return 0;
ES>


Ясно. А как быть с функцией CMyClass::Calculation, чтобы выйти из неё по требованию извне?
Послать сообщение, а внутри этой функции устроить проверки на сообщение (вызов функции GetMessage) с проверкой значения этого сообщения или как-то иначе?

И как быть в случае, если я запускаю поток не с помощью AfxBeginthread, а с помощью CreateThread или _beginthreadex ?
1613 г. = 2024 г.
Re[10]: Как грамотно уничтожить поток в MFC
От: Evgeniy Skvortsov Россия  
Дата: 27.12.16 14:53
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>Ясно. А как быть с функцией CMyClass::Calculation, чтобы выйти из неё по требованию извне?

RF>Послать сообщение, а внутри этой функции устроить проверки на сообщение (вызов функции GetMessage) с проверкой значения этого сообщения или как-то иначе?
Создать два события одно будет означать сигнал к выходу, второе — что нужно что-то посчитать

Тут есть пример как раз рабочего потока который реагирует на 2 события — нужно что-то считать и нужно завершиться.
Правда реализация ожидания из основного потока сделано криво. Он тупо засыпает в ожидании завершения расчёта. Получается никакой асинхронности.
Надо было слать какое-то асинхронное оконное сообщение из потока основному приложению.

RF>И как быть в случае, если я запускаю поток не с помощью AfxBeginthread, а с помощью CreateThread или _beginthreadex ?

То же самое — return 0.
Re[10]: Как грамотно уничтожить поток в MFC
От: Слава  
Дата: 27.12.16 14:58
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>Ясно. А как быть с функцией CMyClass::Calculation, чтобы выйти из неё по требованию извне?

RF>Послать сообщение, а внутри этой функции устроить проверки на сообщение (вызов функции GetMessage) с проверкой значения этого сообщения или как-то иначе?

RF>И как быть в случае, если я запускаю поток не с помощью AfxBeginthread, а с помощью CreateThread или _beginthreadex ?


Они все в итоге вызывают CreateThread, чего бы я и вам желал. Afx... чего-то для COM в начале делает, вам это не нужно. Для завершения треда раньше времени подавайте в его параметрах — там, в структуре LPVOID некий флаг, булевскую переменную, помеченную как volatile. И в ходе вычислений проверяйте этот флаг на истинность, если истинный — прерывайте вычисления и поднимайтесь вверх до самого return 0.
Re[2]: Как грамотно уничтожить поток в MFC
От: pilgrim_ Россия  
Дата: 27.12.16 18:34
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>для приостановки выполнения потока следует воспользоваться функцией SuspendThread, для продолжения выполнения потока--функцией ResumeThread.


Можно, но не нужно. Лучше посылать потоку управляющие команды — напр. путем "посылки" (установки) соотв. событий (НЕ windows сообщений, а тех что CreateEvent), либо соотв. переменных, значение которых поток периодически будет проверять.

RF>А как грамотно уничтожить поток в MFC?


Корректно завершить работу потока — также как и не на MFC, как и в других системах/языках — сказать об этом потоку, как — см. выше.

RF>Мне посоветовали функцию TerminateThread,


Кто посоветовал? Приведи поставь сюда (c)

Не надо использовать TerminateThread.

RF>но у неё есть недостатки--после её вызов поток всё равно занимает стек памяти и имеет доступ к ядру до своего завершения.


У нее куча недостатков, не поленись, обязательно прочитай TerminateThread
И стэк потока пожалуй единственное что освобождается (был правда баг, читай по ссылке: Windows Server 2003 and Windows XP: The target thread's initial stack is not freed, causing a resource leak.), вот тебе выжимка:

TerminateThread is a dangerous function that should only be used in the most extreme cases. You should call TerminateThread only if you know exactly what the target thread is doing, and you control all of the code that the target thread could possibly be running at the time of the termination. For example, TerminateThread can result in the following problems:
If the target thread owns a critical section, the critical section will not be released.
If the target thread is allocating memory from the heap, the heap lock will not be released.
If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread's process could be inconsistent.
If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL.


+ не будут вызваны деструкторы локальных объектов.
+ если используешь MFC AfxBeginThread — всякие дополнительные штуки, как и сам CWinThread, которые создает MFC, также не будут освобождены

RF>А как грамотно уничтожить поток, чтобы у него после этого уничтожения--чтобы после этого уничтожения этот поток не выполнялся, не имел доступа к ядру и не занимал стек памяти?


см. выше, в самом начале

RF>Есть функции AfxEndThread и ExitThread, вызываемые внутри функции потока для его завершения. Но как правильно их использовать?


Не нужно тебе их использовать, просто делай возврат (return) из функции потока, а будешь использовать см.

не будут вызваны деструкторы локальных объектов.

Re[11]: Как грамотно уничтожить поток в MFC
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 28.12.16 08:12
Оценка:
Здравствуйте, Слава, Вы писали:

RF>>И как быть в случае, если я запускаю поток не с помощью AfxBeginthread, а с помощью CreateThread или _beginthreadex ?


С>Они все в итоге вызывают CreateThread, чего бы я и вам желал. Afx... чего-то для COM в начале делает, вам это не нужно. Для завершения треда раньше времени подавайте в его параметрах — там, в структуре LPVOID некий флаг, булевскую переменную, помеченную как volatile. И в ходе вычислений проверяйте этот флаг на истинность, если истинный — прерывайте вычисления и поднимайтесь вверх до самого return 0.


То есть код должен иметь примерно такой вид:

typedef struct
{
  // поля структуры;
  bool  terminate;
} MyData;

DWORD WINAPI  MyThreadFunc(LPVOID lpParam)
{
  MyData* pMyData = (*MyData)lpParam;
  // действия функции;
  if (pMyData->terminate==true)
  {
    // освобождение ресурсов;
    return 0;
  }
  // действия функции;
  if (pMyData->terminate==true)
  {
    // освобождение ресурсов;
    return 0;
  }
  // действия функции;
  return 0; // завершение работы функции MyThreadFunc
}


?
1613 г. = 2024 г.
Re[12]: Как грамотно уничтожить поток в MFC
От: Evgeniy Skvortsov Россия  
Дата: 28.12.16 08:55
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>То есть код должен иметь примерно такой вид:


Нет. В таком цикле поток будет постоянно съедать процессорное время.
Надо использовать события и ожидать их в потоке через WaitForMultipleObjects

Чисто для примера:
HANDLE hEvents[2];

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    DWORD dwEvent = 0;
    bool bExit = false;
    while (!bExit)
    {
        dwEvent = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
        switch (dwEvent)
        {
        case WAIT_OBJECT_0:    
            // событие на выход
            bExit = true;
            break;
        case WAIT_OBJECT_0 + 1:    
            //событие надо считать
            // тут идут какие-то расчёты
            break;
        default:
            // что-то пошло не так, выходим
            bExit = true;
        }
    }
    return dwEvent;
}
int main()
{
    hEvents[0] = CreateEvent(NULL, FALSE, FALSE, NULL);        // событие ожидается в потоке когда поток надо завершить
    hEvents[1] = CreateEvent(NULL, FALSE, FALSE, NULL);        // событие ожидается в потоке когда надо просыпаться и что-то считать
    
    DWORD tid = 0;
    CreateThread(NULL, 0, ThreadProc, NULL, 0, &tid);        // создаем рабочий поток
    SetEvent(hEvents[1]);                        // надо считать
    return 0;
}


В MFC есть какие-то классы обёртки над всей этой многопоточностью, но я слабо разбирался с MFC реализацией. Надо читать книги по этой библиотеке.

Вообще, на мой взгляд многопоточность это не та тема, которую можно освоить за 5 минут на форуме. Нужно читать соответствующую литературу.
Отредактировано 28.12.2016 8:58 Evgeniy Skvortsov . Предыдущая версия .
Re[12]: Как грамотно уничтожить поток в MFC
От: Слава  
Дата: 28.12.16 09:08
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>То есть код должен иметь примерно такой вид:


RF>
RF>typedef struct
RF>{
RF>  // поля структуры;
RF>  bool  terminate;
RF>} MyData;
...
RF>


RF>?


Да, так. Я бы только сделал освобождение ресурсов в одном месте плюсплюсоугодно, то есть так, как его принято делать в C++ — через области видимости, деструкторы или как там еще. Создал бы еще одну функцию DoWork, в которой исполнялась бы вся работа, а ее вызов из MyThreadFunc обернул бы в try... catch, чтобы например деление на ноль не вылетело и не убило весь процесс.

Наконец, terminate я бы сделал как volatile bool terminate, чтобы она всегда читалась из памяти — где лежит, а не из какого бы то ни было кэша. Но тут спорный момент, который я не понимаю, вот обсуждения:

Здесь утверждают, что volatile в С++ работает как надо http://stackoverflow.com/questions/6995310/is-volatile-bool-for-thread-control-considered-wrong

А здесь, что в volatile является не плюсплюсоугодным и может не работать http://stackoverflow.com/questions/29633222/c-stdatomicbool-and-volatile-bool
И предлагают вместо него использовать std::atomic<bool> terminate(false)

Я такого не понимаю, если это ключевое слово языка, то зачем его ломать, а если его сломали, то почему бы тогда не выдавать ошибку компиляции при использовании сломанного? В общем, сиплюсплюс такой сиплюсплюс.
Re[13]: Как грамотно уничтожить поток в MFC
От: Слава  
Дата: 28.12.16 09:16
Оценка:
Здравствуйте, Evgeniy Skvortsov, Вы писали:

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


RF>>То есть код должен иметь примерно такой вид:


ES>Нет. В таком цикле поток будет постоянно съедать процессорное время.


Вы обсуждение целиком прочитали? Где вы видите в приведенном коде какой бы то ни было бесконечный цикл? Этот тред создается для единоразового запуска расчетов, он для того и предназначен, чтобы создаться, отъесть процессорное время и помереть. А проверка флага отмены точно так же, как у RussianFellow, сделана в C# CancellationToken.

То, что предлагаете вы, это создание постоянного треда, который будет постоянно ждать запросов на какие-то расчеты. Смысл в этом есть, но тогда уже можно вообще воспользоваться виндовым пулом тредов, а это еще сложнее для топикстартера.

ES>Вообще, на мой взгляд многопоточность это не та тема, которую можно освоить за 5 минут на форуме. Нужно читать соответствующую литературу.


Ну почему же, если пару-тройку дней поэкспериментировать, то научиться вполне можно. По крайней мере, появится понимание того, о чём в литературе пишут.
Re[14]: Как грамотно уничтожить поток в MFC
От: Evgeniy Skvortsov Россия  
Дата: 28.12.16 09:54
Оценка:
Здравствуйте, Слава, Вы писали:

С>Вы обсуждение целиком прочитали? Где вы видите в приведенном коде какой бы то ни было бесконечный цикл? Этот тред создается для единоразового запуска расчетов, он для того и предназначен, чтобы создаться, отъесть процессорное время и помереть. А проверка флага отмены точно так же, как у RussianFellow, сделана в C# CancellationToken.


Сори, не внимательно прочитал.
Re[13]: Как грамотно приостановить и запустить на продолжение поток в MFC
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 28.12.16 11:45
Оценка:
Здравствуйте, Слава, Вы писали:

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


RF>>То есть код должен иметь примерно такой вид:


RF>>
RF>>typedef struct
RF>>{
RF>>  // поля структуры;
RF>>  bool  terminate;
RF>>} MyData;
С>...
RF>>


RF>>?


С>Да, так.


Ясно.

У меня возник такой вопрос--а как правильно приостановить поток и как правильно запустить на продолжение приостановленный поток?
Если нежелательно использовать функции SuspendThread и ResumeThread;
Вот код:

typedef struct
{
  // поля структуры;
  int workflag; // флаг: 0--поток выполняется, 1--завершить поток, 2--приостановить поток, 3--запустить на продолжение приостановленный поток
} MyData;

DWORD WINAPI  MyThreadFunc(LPVOID  lpParam)
{
  MyData *pMyData = (MyData*)lpParam;
  // действия функции;
  if (pMyData->workflag==1)
  // завершение потока
  {
    // освобождение ресурсов;
    return 0;
  }
  if (pMyData->workflag==2)
  // приостановка потока
  {
    // освобождение ресурсов, если необходимо;
    while ((pMyData->workflag!=1)&&(pMyData->workflag!=3)); // ждём, пока не появится команда завершить поток или продолжить выполнение потока
    if (pMyData->workflag==1)
    // завершение потока
    {
      // освобождение ресурсов;
      return 0;
    }
    if (pMyData->workflag==3)
    // продолжение исполнения потока
    {
      // подключить ресурсы, если необходимо;
      workflag = 0;
    }
  }
  // действия функции;
  if (pMyData->workflag==2)
  // приостановка потока
  {
    // освобождение ресурсов, если необходимо;
    while ((pMyData->workflag!=1)&&(pMyData->workflag!=3)); // ждём, пока не появится команда завершить поток или продолжить выполнение потока
    if (pMyData->workflag==1)
    // завершение потока
    {
      // освобождение ресурсов;
      return 0;
    }
    if (pMyData->workflag==3)
    // продолжение исполнения потока
    {
      // подключить ресурсы, если необходимо;
      workflag = 0;
    }
  }
  // действия функции;
  return 0; // завершение работы функции MyThreadFunc
}


Правильно ли у меня написано? (Функция в потоке последовательно выполняет действия, а потом завершается. Никаких бесконечных циклов в ней нет).
1613 г. = 2024 г.
Re[14]: Как грамотно приостановить и запустить на продолжение поток в MFC
От: Слава  
Дата: 28.12.16 12:10
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>У меня возник такой вопрос--а как правильно приостановить поток и как правильно запустить на продолжение приостановленный поток?

RF>Если нежелательно использовать функции SuspendThread и ResumeThread;
RF>Вот код:

RF>
RF>typedef struct
RF>{
RF>  // поля структуры;
RF>  int workflag; // флаг: 0--поток выполняется, 1--завершить поток, 2--приостановить поток, 3--запустить на продолжение приостановленный поток
RF>} MyData;
RF>


Я вам писал, что флаг должен быть помечен либо как volatile, либо std::atomic. Так как у вас написано — может работать через раз.

RF>Правильно ли у меня написано? (Функция в потоке последовательно выполняет действия, а потом завершается. Никаких бесконечных циклов в ней нет).


Моя рекомендация про один булевый флаг имеет смысл только для одной простой задачи — отмена выполнения потока. Для того, что хотите сделать вы, с приостановкой и возобновлением — лучше использовать рекомендации Скворцова, посмотрите, что он вам писал.

Но вообще, вы сами подумайте — что такое "приостановить поток"? А если он прямо сейчас из сети читает? Или делает что-то еще — захватил ресурсы, которые долго держать нельзя, а тут вы его приостановили, ресурсы держатся, а так делать нежелательно. Я не знаю точно, как работает менеджер памяти нынешнего С++ (их вообще много), но я думаю, что в многопоточном окружении он вполне может захватывать какую-нибудь блокировку при вызове new, и если SuspendThread остановит выполнение именно в это время, то после этого все остальные вызовы new будут зависать.

Обычно для остановки делаются какие-то безопасные точки, места в которых можно остановиться с гарантией того, что ничего не сломается. SuspendThread таких гарантий не даёт, вам его правильно отсоветовали.

Лично я впервые вижу такую задачу "приостановить" вычисления. Для чего это вообще надо? Зачем? Я делал подобное, но не для вычислительных задач, а для чего-то схожего с документооборотом. Зачем вам приостанавливать вычисления?
Re[15]: Как грамотно приостановить и запустить на продолжение поток в MFC
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 28.12.16 12:57
Оценка:
Здравствуйте, Слава, Вы писали:

С>Но вообще, вы сами подумайте — что такое "приостановить поток"? А если он прямо сейчас из сети читает? Или делает что-то еще — захватил ресурсы, которые долго держать нельзя, а тут вы его приостановили, ресурсы держатся, а так делать нежелательно. Я не знаю точно, как работает менеджер памяти нынешнего С++ (их вообще много), но я думаю, что в многопоточном окружении он вполне может захватывать какую-нибудь блокировку при вызове new, и если SuspendThread остановит выполнение именно в это время, то после этого все остальные вызовы new будут зависать.


С>Лично я впервые вижу такую задачу "приостановить" вычисления. Для чего это вообще надо? Зачем? Я делал подобное, но не для вычислительных задач, а для чего-то схожего с документооборотом. Зачем вам приостанавливать вычисления?


Ну, например, делаются какие-то вычисления. Результаты выводятся в консольное окно (никакие другие ресурсы при этом не захватываются). Это консольное окно находится на заднем плане экрана--а на переднем плане находится диалоговое окно приложения. И если пользователь хочет посмотреть процесс вычислений, то при нажатии на кнопку диалогового окна на передний план экрана помещается консольное окно, в которое выводятся результаты вычислений--пользователь видит, что было вычислено к текущему моменту. А потом, при нажатии на другую кнопку диалогового окна, это диалоговое окно опять помещается на передний план экрана, а процесс вычислений продолжается.
1613 г. = 2024 г.
Re[15]: Как грамотно приостановить и запустить на продолжение поток в MFC
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 28.12.16 13:14
Оценка:
Здравствуйте, Слава, Вы писали:

С>Моя рекомендация про один булевый флаг имеет смысл только для одной простой задачи — отмена выполнения потока. Для того, что хотите сделать вы, с приостановкой и возобновлением — лучше использовать рекомендации Скворцова, посмотрите, что он вам писал.


Евгений мне написал про другой случай--когда есть цикл (вообще говоря, бесконечный), в котором происходит анализ событий.
Мне это не подходит. У меня есть последовательный алгоритм, который завершается при отсутствии воздействия извне и на этом поток уничтожается. Но извне также можно принудительно, раньше времени завершить поток. Также извне можно принудительно приостановить поток, а потом его или принудительно продолжить, или принудительно завершить раньше времени.

Мне кажется, что такая конструкция допустима:

if (pMyData->workflag==2)
// приостановка потока
{
  // освобождение ресурсов, если необходимо;
  while ((pMyData->workflag!=1)&&(pMyData->workflag!=3)); // ждём, пока не появится команда завершить поток или продолжить выполнение потока
  if (pMyData->workflag==1)
  // завершение потока
  {
    // освобождение ресурсов;
    return 0;
  }
  if (pMyData->workflag==3)
  // продолжение исполнения потока
  {
    // подключить ресурсы, если необходимо;
    workflag = 0;
  }
}


В крайнем случае, её можно немного изменить:

if (pMyData->workflag==2)
// приостановка потока
{
  // освобождение ресурсов, если необходимо;
  while ((pMyData->workflag!=1)&&(pMyData->workflag!=3))
  // ждём, пока не появится команда завершить поток или продолжить выполнение потока
  {
    Sleep(1000); // задержка выполнения потока на одну секунду, в функции Sleep можно поставить любое другое число
  }
  if (pMyData->workflag==1)
  // завершение потока
  {
    // освобождение ресурсов;
    return 0;
  }
  if (pMyData->workflag==3)
  // продолжение исполнения потока
  {
    // подключить ресурсы, если необходимо;
    workflag = 0;
  }
}
1613 г. = 2024 г.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.