Здравствуйте, found, Вы писали:
F>В конструкторе необходимо запустить поток, в котором сигнализировать событием объекта и остановиться в ожидании внешнего события.
Крайне не рекомендую так делать. Нужен объект связанный с потоком — сделайте конструирование двухфазным, что-нибудь в этом духе:
Здравствуйте, found, Вы писали:
F>Здравствуйте.
F>Читал в разных местах, что с одной стороны использование this в конструкторе — нормальная практика, с другой стороны нет. F>Опишу задачу упрощенно: при конструировании объекта создать, связанный с ним поток, использующий некоторые поля объекта. F>Пример:
F>
В данном примере проблемы нет.
F>Можно ли поступать подобным образом? Передавать this во внешнюю функцию, которая вызывает статическую функцию с этим параметром, далее относительно него запускать функцию член.
Если от этого класса не наследуются, и это последний конструктор то из него можно всё запускать.
F>Реальная задача: F>Внутри объекта хранится объект — событие и ссылка на внешнее событие. В конструкторе необходимо запустить поток, в котором сигнализировать событием объекта и остановиться в ожидании внешнего события. F>В итоге происходит следующая последовательность действий: F>1. В конструкторе создается объект событие F>2. В конструкторе создается поток, приемом описанным выше F>3. Внутри функции потока событие объекта устанавливается в сигнальное состояние F>4. Функция потока останавливается в ожидании внешнего события
F>Как правило, приведенная схема работает. Но когда потоков становится действительно много, иногда возникает ситуация, когда пункт 3 алгоритма приводит к ошибке обращения к памяти, при чем с помощью отладчика, удалось выяснить, что неверен не только объект внутреннего события, но и все остальные поля класса, из чего я делаю вывод, что неверен указатель this, относительно которого вызывается функция член класса.
Вот список возможных проблем:
* Время жизни объекта короче времени жизни потока. От сюда: кирдык.
* Вызываемая функция не потокобезопасна(как в примере: ++ там не защищён)
* Вызов виртуальной функции из потока, равно как и вызов виртуальной функции из конструктора = UB
* dynamic_cast из потока, равно как и из конструктора = UB
* исключение брошенное из дальнейшего кода в конструторе приведёт в конечном счёте к инвалидации объекта
* исключение при инициализации следующего из массива объекта Foo oops[3]; приведёт в конечном счёте к инвалидации объекта
F>В чем может быть реальная проблема в пункте 3?
я думаю этот список легко дополнить... у тебя какой вариант?
Читал в разных местах, что с одной стороны использование this в конструкторе — нормальная практика, с другой стороны нет.
Опишу задачу упрощенно: при конструировании объекта создать, связанный с ним поток, использующий некоторые поля объекта.
Пример:
Можно ли поступать подобным образом? Передавать this во внешнюю функцию, которая вызывает статическую функцию с этим параметром, далее относительно него запускать функцию член.
Реальная задача:
Внутри объекта хранится объект — событие и ссылка на внешнее событие. В конструкторе необходимо запустить поток, в котором сигнализировать событием объекта и остановиться в ожидании внешнего события.
В итоге происходит следующая последовательность действий:
1. В конструкторе создается объект событие
2. В конструкторе создается поток, приемом описанным выше
3. Внутри функции потока событие объекта устанавливается в сигнальное состояние
4. Функция потока останавливается в ожидании внешнего события
Как правило, приведенная схема работает. Но когда потоков становится действительно много, иногда возникает ситуация, когда пункт 3 алгоритма приводит к ошибке обращения к памяти, при чем с помощью отладчика, удалось выяснить, что неверен не только объект внутреннего события, но и все остальные поля класса, из чего я делаю вывод, что неверен указатель this, относительно которого вызывается функция член класса.
Здравствуйте, Caracrist, Вы писали:
F>>Реальная задача: F>>Внутри объекта хранится объект — событие и ссылка на внешнее событие. В конструкторе необходимо запустить поток, в котором сигнализировать событием объекта и остановиться в ожидании внешнего события. F>>В итоге происходит следующая последовательность действий: F>>1. В конструкторе создается объект событие F>>2. В конструкторе создается поток, приемом описанным выше F>>3. Внутри функции потока событие объекта устанавливается в сигнальное состояние F>>4. Функция потока останавливается в ожидании внешнего события
F>>Как правило, приведенная схема работает. Но когда потоков становится действительно много, иногда возникает ситуация, когда пункт 3 алгоритма приводит к ошибке обращения к памяти, при чем с помощью отладчика, удалось выяснить, что неверен не только объект внутреннего события, но и все остальные поля класса, из чего я делаю вывод, что неверен указатель this, относительно которого вызывается функция член класса. C>Вот список возможных проблем: C>* Время жизни объекта короче времени жизни потока. От сюда: кирдык. C>* Вызываемая функция не потокобезопасна(как в примере: ++ там не защищён) C>* Вызов виртуальной функции из потока, равно как и вызов виртуальной функции из конструктора = UB C>* dynamic_cast из потока, равно как и из конструктора = UB C>* исключение брошенное из дальнейшего кода в конструторе приведёт в конечном счёте к инвалидации объекта C>* исключение при инициализации следующего из массива объекта Foo oops[3]; приведёт в конечном счёте к инвалидации объекта
F>>В чем может быть реальная проблема в пункте 3?
C>я думаю этот список легко дополнить... у тебя какой вариант?
* В деструкторе объекта поток при необходимости завершается, время жизнь объекта > времени жизни потока
* Сам вызов функции происходит в потоке следующим образом:
вход в критическую секцию, привязанную к конкретному объекту, вызов функции, которая сигнализирует событие, ожидание внешнего события, выход из критической секции.
* Класс ни от чего не наследуется и от него тоже ничего не наслудется
* Есть только static_cast при запуске потока, описанный в примере
* Дальше кода в конструкторе нет, предполагал данный вариант, поэтому переместил создание потока в самый конец
* Этот вариант интересен, потому что при создании мини дампа средствами Win Debugging Tools, ошибка характеризуется именно как попытка установки сигнального состояния для объекта, который не является событием. Возможно, ошибка происходит где-то в ином месте и разрушает кучу. Единственный нюанс этой теории: если вынести запуск потока в отдельную функцию-член класса, т.е. вместо: new Foo(...), писать
Foo* obj = new Foo(...);
obj->init();
то все работает без проблем. Не отрицаю, что проблема может просто скрываться дополнительными тактами необходимыми для вызова функции. И ещё хочу заметить, что в программе нет массива объектов, а каждый элемент является элементом динамического списка.
Сам я фактически перебрал все перечисленные варианты. Мне кажется, что зацепками могут являться два следующих факта:
1. При отладке внешним отладчиком при генерации исключения в пункте 3, неверными являются все поля объекта, относительно которого вызывается функция потока.
Соответственно, либо объект передается в функцию изначально испорченным (маловероятно), либо портится другими потоками во время исполнения (если так, то необходимо мне дальше самому разбираться, чтобы не выкладывать относительно много кода).
2. При выносе функции запуска потока за пределы конструктора (сейчас использую это решение) ошибка пропадает. В данном случае возникает вопрос, решена ли сама проблема, либо же она только замаскирована.
Думаю просмотреть сейчас сам код, который генерирует компилятор, в поисках иных зацепок. Больше вариантов, к сожалению, нет.
Спасибо за направление, в котором смотреть. Сейчас делаю точно тоже самое, но значительно в менее красивой форме: за new следует вызов init.
Переделаю решение, используя ваш пример.
Здравствуйте, found, Вы писали:
F>* В деструкторе объекта поток при необходимости завершается, время жизнь объекта > времени жизни потока
Как объект завершает поток? (надеюсь не TerminateThread...) У него есть хендл на него? А если не успеет записать хендл до вызова деструктора? Между созданием потока и возвращением из CreateThread может многое произойти...
F>* Сам вызов функции происходит в потоке следующим образом: F>вход в критическую секцию, привязанную к конкретному объекту, вызов функции, которая сигнализирует событие, ожидание внешнего события, выход из критической секции.
Очень экзотические краши бывают при попытке зайти в невалидную критическую секцию...
F>* Класс ни от чего не наследуется и от него тоже ничего не наслудется F>* Есть только static_cast при запуске потока, описанный в примере F>* Дальше кода в конструкторе нет, предполагал данный вариант, поэтому переместил создание потока в самый конец
Это хорошо, деструкторы не в счёт.
F>Единственный нюанс этой теории: если вынести запуск потока в отдельную функцию-член класса, т.е. вместо: new Foo(...), писать F>
F>Foo* obj = new Foo(...);
obj->>init();
F>
это кстати всегда хорошо
F>Думаю просмотреть сейчас сам код, который генерирует компилятор, в поисках иных зацепок. Больше вариантов, к сожалению, нет.
Здравствуйте, Caracrist, Вы писали:
C>Как объект завершает поток? (надеюсь не TerminateThread...) У него есть хендл на него? А если не успеет записать хендл до вызова деструктора? Между созданием потока и возвращением из CreateThread может многое произойти...
В данный момент гарантируется тот факт, что объект не может быть уничтожен до завершения потока, логикой работы программы.
Раньше использовал TerminateThread, какие нюансы могут при этом возникать. Мои рассуждения, сводились к тому, что насильно уничтожается стек объекта, что может приводит к нежелательным последствиям. Может ли вызов этой функции влиять на другие потоки и кучу приложения?
F>>* Сам вызов функции происходит в потоке следующим образом: F>>вход в критическую секцию, привязанную к конкретному объекту, вызов функции, которая сигнализирует событие, ожидание внешнего события, выход из критической секции. C>Очень экзотические краши бывают при попытке зайти в невалидную критическую секцию...
Критическая секция инициализируется с помощью функции InitializeCriticalSection внутри конструктора до создания потока. Но, т.к. , все поля класса имеют невалидные значения в отладчике во время ошибки, то и критическая секция является неверной.
С утра внимательно просмотрю код, возможно, не вижу какой-то банальной ошибки. Либо просто, действительно, не стоит в принципе создавать поток внутри конструктора. Но т.к. проект учебный интересно докопаться до нюансов.
Здравствуйте, found, Вы писали:
F>Раньше использовал TerminateThread, какие нюансы могут при этом возникать. Мои рассуждения, сводились к тому, что насильно уничтожается стек объекта, что может приводит к нежелательным последствиям. Может ли вызов этой функции влиять на другие потоки и кучу приложения?
Если поток в критической секции, TerminateThread делает её не валидной. Любая операция с CS может привезти крашу. Я уже молчу не выполненом dllmain и __declspec(thread).
Здравствуйте, found, Вы писали: F>Можно ли поступать подобным образом? Передавать this во внешнюю функцию, которая вызывает статическую функцию с этим параметром, далее относительно него запускать функцию член.
проблемы могут быть если конструктор Foo был вызван при конструировании кого-то наследованного от Foo.
тогда для виртуальных ф-ий еще будет неправильная таблица виртуальных методов, а если кто-то захочет откастить this dynamic_cast ом к наследнику Foo, я даже и не знаю, что будет.
> struct Foo > { > Foo() : data(0) > { > CreateThread(..., Starter,this, ...); > }
> Можно ли поступать подобным образом? Передавать this во внешнюю функцию, которая > вызывает статическую функцию с этим параметром, далее относительно него > запускать функцию член.
Можно.
Только убедись, что этот объект в этой функции не будет использован до момента
полного окончания его инициализации.
В частности, если
CreateThread(..., Starter,this, ...);
-- последний оператор в конструкторе или поток создаётся приотановленным,
то ничего плохого.
Если поток сразу же пускается, и, например, если ещё наследник Foo,
который ещё не завершил выполнение своего конструктора, то будут гонки.
Здравствуйте, found, Вы писали:
F>Читал в разных местах, что с одной стороны использование this в конструкторе — нормальная практика, с другой стороны нет. F>Опишу задачу упрощенно: при конструировании объекта создать, связанный с ним поток, использующий некоторые поля объекта.