Использование _beginthread
От: YourLastSong  
Дата: 01.11.11 16:46
Оценка:
Здравствуйте, уважаемые господа.

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

Я так понял, необходимо сделать примерно так:

HANDLE handle_thread, hStopEvent;

void thread (void *args)
{
do
{
...
} while (WaitForSingleObject (hStopEvent, 0) != WAIT_OBJECT_0); // Проверяем, не установлено ли событие
}

int main (int argc, char *argv[])
{
hStopEvent = CreateEvent (NULL, FALSE, FALSE, NULL); // Создаём событие для того, чтобы потом можно было завершить поток, если всё пойдёт, как надо
handle_thread = reinterpret_cast <HANDLE> (_beginthread (thread, 0, NULL)); // При помощи функции _beginthread создаём поток

... // Создаём ещё неск. потоков

SetEvent (hStopEvent); // Устанавливаем событие
if (WaitForSingleObject (handle_thread, 500) != WAIT_OBJECT_0) // Ждём, пока поток завершится
TerminateThread (handle_thread, (DWORD)-1); // В идеале даная строка вызваться, разумеется, не должна
CloseHandle (handle_thread); // Закрываем хэндл, чтобы освободить память, которая была выделена для потока

... // Закрываем ещё неск. потоков

CloseHandle (hStopEvent); // Закрываем хэндл события, которое мы использовали для завершения потоков только что

return 0;
}


Что тут не так? Почему при таком коде у меня всё равно остаются утечки памяти?

Заранее благодарю за возможные ответы.
Re: Использование _beginthread
От: okman Беларусь https://searchinform.ru/
Дата: 01.11.11 17:34
Оценка:
Здравствуйте, YourLastSong, Вы писали:

YLS>В своей программе мне необходимо реализовать остановку выполнения потоков в опр. момент, причём в любом случае.


YLS>Я так понял, необходимо сделать примерно так:


YLS>
YLS>HANDLE handle_thread, hStopEvent;

YLS>void thread (void *args)
YLS>{
YLS>do
YLS>{
YLS>...
YLS>} while (WaitForSingleObject (hStopEvent, 0) != WAIT_OBJECT_0); // Проверяем, не установлено ли событие
YLS>}

YLS>int main (int argc, char *argv[])
YLS>{
YLS>hStopEvent = CreateEvent (NULL, FALSE, FALSE, NULL); // Создаём событие для того, чтобы потом можно было завершить поток, если всё пойдёт, как надо
YLS>handle_thread = reinterpret_cast <HANDLE> (_beginthread (thread, 0, NULL)); // При помощи функции _beginthread создаём поток

YLS>... // Создаём ещё неск. потоков

YLS>SetEvent (hStopEvent); // Устанавливаем событие
YLS>if (WaitForSingleObject (handle_thread, 500) != WAIT_OBJECT_0) // Ждём, пока поток завершится
YLS>TerminateThread (handle_thread, (DWORD)-1); // В идеале даная строка вызваться, разумеется, не должна
YLS>CloseHandle (handle_thread); // Закрываем хэндл, чтобы освободить память, которая была выделена для потока

YLS>... // Закрываем ещё неск. потоков

YLS>CloseHandle (hStopEvent); // Закрываем хэндл события, которое мы использовали для завершения потоков только что

YLS>return 0;
YLS>}
YLS>


YLS>Что тут не так? Почему при таком коде у меня всё равно остаются утечки памяти?


Во-первых, использовать событие не обязательно (и неэффективно).
Достаточно глобальной флаговой переменной. Только не забудьте снабдить ее ключевым словом volatile,
иначе возможны неприятные побочные эффекты агрессивной оптимизации.

Во-вторых, такое завершение потока не вызывает деструкторы объектов, которые находятся в
области действия функции, и об этом явно сказано в MSDN.
К примеру, есть такой вот класс:
class some_class
{
public:
    some_class()
    {
        std::cout << "Creating..." << std::endl;
    }

    ~some_class()
    {
        std::cout << "Destroying..." << std::endl;
    }
};

Если поместить его внутрь функции потока, деструктор вызван не будет:
unsigned int
_stdcall
ThreadProc(
    __in    VOID      * pParam
    )
{
    some_class SomeClass;
    _endthreadex(0);
    return 0;
}

>Creating...

"Destroying..." напечатано не будет.

В своем коде я использую такой подход:
namespace // Чтобы не возникло конфликта имен ThreadProc и ThreadProc_Impl.
{

// Назначение этой функции заключается в паре дополнительных фигурных скобок,
// которые обеспечивают вызов деструкторов при выходе.
void
_stdcall
ThreadProc_Impl(
    __in    VOID      * pParam
    )
{
    some_class SomeClass;
}

// Точка входа в поток.
unsigned int
_stdcall
ThreadProc(
    __in    VOID      * pParam
    )
{
    ThreadProc_Impl(pParam);
    _endthreadex(0);
    return 0;
}

} // namespace

// ...

HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);

>Creating...
>Destroying...

Re[2]: Использование _beginthread
От: Centaur Россия  
Дата: 01.11.11 17:49
Оценка:
Здравствуйте, okman, Вы писали:

O>// Точка входа в поток.
O>unsigned int
O>_stdcall
O>ThreadProc(
O>    __in    VOID      * pParam
O>    )
O>{
O>    ThreadProc_Impl(pParam);
O>    _endthreadex(0);
O>    return 0;
O>}


Смысла в этой функции — ровным счётом никакого. В функции потока не нужно явно вызывать _endthreadex — достаточно вернуть управление естественным образом.
Re[2]: Использование _beginthread
От: YourLastSong  
Дата: 01.11.11 17:53
Оценка:
O>Во-первых, использовать событие не обязательно (и неэффективно).

Почему?

O>Во-вторых, такое завершение потока не вызывает деструкторы объектов, которые находятся в

O>области действия функции, и об этом явно сказано в MSDN.

При каком именно завершении потока? При помощи return или как?

Зачем использовать функцию _endthread явно вообще? Ведь при выходе из функции потока функция _endthread должна вызваться самостоятельно и освободить память, разве нет?

Сделал примерно так:


HANDLE handle_thread;
volatile bool thread_flag;

void thread (void *args)
{
do
{
...
} while (!thread_flag);
}

int main (int argc, char *argv[])
{
thread_flag = false;
handle_thread = reinterpret_cast <HANDLE> (_beginthread (thread, 0, NULL));

...

thread_flag = true;
if (WaitForSingleObject (handle_thread, 500) != WAIT_OBJECT_0)
TerminateThread (handle_thread, (DWORD)-1);
CloseHandle (handle_thread);

...

return 0;
}


Однако даже так утечки памяти остались, разумеется.
Re: Использование _beginthread
От: Centaur Россия  
Дата: 01.11.11 17:57
Оценка:
Здравствуйте, YourLastSong, Вы писали:

YLS>В своей программе мне необходимо реализовать остановку выполнения потоков в опр. момент, причём в любом случае.


Этого нельзя делать. Вы остановите поток в тот момент, когда он занимает какой-нибудь ресурс, критически необходимый всей программе. Например, кучу или же OS Loader Lock. Ресурс останется занятым, и программа не сможет сделать больше ничего полезного, кроме как завершиться.
Re[2]: Использование _beginthread
От: YourLastSong  
Дата: 01.11.11 18:02
Оценка:
C>Этого нельзя делать. Вы остановите поток в тот момент, когда он занимает какой-нибудь ресурс, критически необходимый всей программе.

В смысле? Ну хоть как-то же можно остановить, верно?

При помощи TerminateThread я, разумеется, знаю, что завершать поток не надо.

C>Например, кучу или же OS Loader Lock.


У меня этого в потоках всё равно не используется.
Re[3]: Использование _beginthread
От: Ops Россия  
Дата: 01.11.11 19:31
Оценка:
Здравствуйте, YourLastSong, Вы писали:

C>>Например, кучу или же OS Loader Lock.


YLS>У меня этого в потоках всё равно не используется.


Кучу не используешь? Какие тогда утечки?
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[4]: Использование _beginthread
От: YourLastSong  
Дата: 01.11.11 19:55
Оценка:
Ops>Кучу не используешь? Какие тогда утечки?

Не знаю, но происходит что-то явно странное, причём crtdbg.h ничего не показывает.
Re[3]: Использование _beginthread
От: okman Беларусь https://searchinform.ru/
Дата: 01.11.11 20:54
Оценка:
Здравствуйте, Centaur, Вы писали:

C>Смысла в этой функции — ровным счётом никакого. В функции потока не нужно явно вызывать _endthreadex — достаточно вернуть управление естественным образом.


Польза такого оформления именно смысловая, чем физическая, так как _endthread(ex) все равно будет вызвана.
А явный вызов ее в ThreadProc указывает на то, какой функцией поток создавался, — CreateThread или
_beginthread(ex), — и какие тонкости из этого вытекают. У _beginthread есть такая особенность — если
функция потока выполнится за короткое время, вызывающему будет возвращен неверный код. И по этой же
причине wait-функции могут некорректно работать с дескрипторами потоков, созданных при помощи beginthread.
Re[3]: Использование _beginthread
От: okman Беларусь https://searchinform.ru/
Дата: 01.11.11 20:55
Оценка:
Здравствуйте, YourLastSong, Вы писали:

O>>Во-первых, использовать событие не обязательно (и неэффективно).


YLS>Почему?


Потому что wait-функции — это накладно.
А чтение значения переменной обойдется в несколько тактов процессора.

O>>Во-вторых, такое завершение потока не вызывает деструкторы объектов, которые находятся в

O>>области действия функции, и об этом явно сказано в MSDN.

YLS>При каком именно завершении потока? При помощи return или как?


Я о TerminateThread. Деструкторы объектов C++ не будут вызваны.

YLS>Зачем использовать функцию _endthread явно вообще? Ведь при выходе из функции потока функция _endthread должна вызваться самостоятельно и освободить память, разве нет?


Вместо _beginthread/_endthread лучше использовать _beginthreadex/_endthreadex.
О причинах подробно написано в MSDN.

YLS>Сделал примерно так:


YLS>
YLS>...
YLS>thread_flag = true;
YLS>if (WaitForSingleObject (handle_thread, 500) != WAIT_OBJECT_0)
YLS>TerminateThread (handle_thread, (DWORD)-1);
YLS>CloseHandle (handle_thread);
YLS>...
YLS>


Невинный код, таящий неочевидную, хоть и очень маловероятную, ошибку.
Дело в том, что дескриптор потока, созданного _beginthread, автоматически закрывается при
уничтожении потока. Это значит, что если после строчки "thread_flag = true" поток завершит
свою работу до вызова функции WaitForSingleObject, она будет работать с уже невалидным дескриптором.
Который, кстати, система может назначить другому потоку. А там и TerminateThread...
Короче, _beginthread/_endthread — ненадежно, надо юзать их аналоги _beginthreadex/_endthreadex,
там таких проблем нету.
Re[4]: Использование _beginthread
От: YourLastSong  
Дата: 02.11.11 05:56
Оценка:
O>Невинный код, таящий неочевидную, хоть и очень маловероятную, ошибку.
O>Дело в том, что дескриптор потока, созданного _beginthread, автоматически закрывается при
O>уничтожении потока. Это значит, что если после строчки "thread_flag = true" поток завершит
O>свою работу до вызова функции WaitForSingleObject, она будет работать с уже невалидным дескриптором.
O>Который, кстати, система может назначить другому потоку. А там и TerminateThread...
O>Короче, _beginthread/_endthread — ненадежно, надо юзать их аналоги _beginthreadex/_endthreadex,
O>там таких проблем нету.

А если использовать CreateThread таким же образом?


HANDLE handle_thread;
volatile bool thread_flag;

DWORD __stdcall thread (void *args)
{
do
{
...
} while (!thread_flag);
return 0;
}

int main (int argc, char *argv[])
{
thread_flag = false;
handle_thread = CreateThread (NULL, 0, thread, NULL, 0, NULL));

...

thread_flag = true;
if (WaitForSingleObject (handle_thread, 500) != WAIT_OBJECT_0)
TerminateThread (handle_thread, (DWORD)-1);
CloseHandle (handle_thread);

...

return 0;
}


Так тоже будет прав. или всё же нет вообще?
Re[5]: Использование _beginthread
От: okman Беларусь https://searchinform.ru/
Дата: 02.11.11 06:47
Оценка:
Здравствуйте, YourLastSong, Вы писали:

YLS>А если использовать CreateThread таким же образом?

YLS>...
YLS>Так тоже будет прав. или всё же нет вообще?

Так нормально. Только CreateThread ведет к утечкам памяти. Очень небольшим, но это морально неприятно.
Все же _beginthreadex/_endthreadex — лучший выбор из возможных вариантов.
Re[5]: Использование _beginthread
От: Jolly Roger  
Дата: 02.11.11 06:53
Оценка:
Здравствуйте, YourLastSong, Вы писали:

1) TerminateThread срабатывает? Если да, то утечки будут, даже если функция потока вообще ничего не делает — при TerminateThread не освобождается стек потока.
2) Что за утечки, какой памяти, как Вы их определяете?
3) Если временно удалить вызов TerminateThread, проблема исчезает?
"Нормальные герои всегда идут в обход!"
Re: Использование _beginthread
От: о_О
Дата: 02.11.11 07:26
Оценка:
Здравствуйте, YourLastSong, Вы писали:

ты уже прочитал это? я спрашиваю потому, что если бы да, такого вопроса не было.
касательно остановки потоков могу сказать следующее. TerminateThread должна вызываться только при завершении приложения. в другом случае возможен только метод зомби — если срабатывает тайм-аут, ты ставишь флаг, что поток не нужен. поток, перед определенными действиями, проверяет флаг и в случае не нужности завершается сам.
Re[2]: Использование _beginthread
От: о_О
Дата: 02.11.11 07:28
Оценка:
Здравствуйте, о_О, Вы писали:

о_О>Здравствуйте, YourLastSong, Вы писали:


о_О>ты уже прочитал это? я спрашиваю потому, что если бы да, такого вопроса не было.

о_О>касательно остановки потоков могу сказать следующее. TerminateThread должна вызываться только при завершении приложения. в другом случае возможен только метод зомби — если срабатывает тайм-аут, ты ставишь флаг, что поток не нужен. поток, перед определенными действиями, проверяет флаг и в случае не нужности завершается сам.

ссылка выпилилась http://www.ozon.ru/context/detail/id/116668/
Re[3]: Использование _beginthread
От: о_О
Дата: 02.11.11 07:29
Оценка:
Здравствуйте, о_О, Вы писали:

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


о_О>>Здравствуйте, YourLastSong, Вы писали:


о_О>>ты уже прочитал это? я спрашиваю потому, что если бы да, такого вопроса не было.

о_О>>касательно остановки потоков могу сказать следующее. TerminateThread должна вызываться только при завершении приложения. в другом случае возможен только метод зомби — если срабатывает тайм-аут, ты ставишь флаг, что поток не нужен. поток, перед определенными действиями, проверяет флаг и в случае не нужности завершается сам.

о_О>ссылка выпилилась http://www.ozon.ru/context/detail/id/116668/


че за хрень?! ozon.ru/context/detail/id/116668/
Re[6]: Использование _beginthread
От: YourLastSong  
Дата: 02.11.11 15:02
Оценка:
O>Так нормально. Только CreateThread ведет к утечкам памяти. Очень небольшим, но это морально неприятно.

Почему?

Так всё равно остаются утечки памяти.
Re[6]: Использование _beginthread
От: YourLastSong  
Дата: 02.11.11 15:04
Оценка:
JR>Что за утечки, какой памяти, как Вы их определяете?

Смотрю через диспетчер задач.

Например, через диспетчер задач видно, что память не освобождается при выходе из потока.

JR>Если временно удалить вызов TerminateThread, проблема исчезает?


Нет, только что проверил.
Re: Использование _beginthread
От: YourLastSong  
Дата: 02.11.11 16:12
Оценка:
Странно.

Дело не в потоках вообще — убрал их, однако утечки памяти всё равно остались.

Кол-во операторов new совпадает с delete, а new[] — с delete[], malloc не использую.

Из-за чего ещё они могут возникать?

К сожалению, сейчас crtdbg.h ни о чём не сообщает.
Re[7]: Использование _beginthread
От: okman Беларусь https://searchinform.ru/
Дата: 02.11.11 16:14
Оценка:
Здравствуйте, YourLastSong, Вы писали:

YLS>Так всё равно остаются утечки памяти.


О каких конкретно утечках идет речь ?
Что течет — дескрипторы, память, или не разрушаются какие-то объекты ?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.