Снова this в конструкторе
От: found Россия ak239.ru
Дата: 01.03.12 19:19
Оценка:
Здравствуйте.

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

struct Foo
{
  Foo() : data(0)
  {
    CreateThread(..., Starter, this, ...);
  }

  static int Starter(void* obj)
  {
    static_cast<Foo*>(obj)->run();
  }

  void run()
  {
    m_Data++;
  }

  int m_Data;
}


Можно ли поступать подобным образом? Передавать this во внешнюю функцию, которая вызывает статическую функцию с этим параметром, далее относительно него запускать функцию член.

Реальная задача:
Внутри объекта хранится объект — событие и ссылка на внешнее событие. В конструкторе необходимо запустить поток, в котором сигнализировать событием объекта и остановиться в ожидании внешнего события.
В итоге происходит следующая последовательность действий:
1. В конструкторе создается объект событие
2. В конструкторе создается поток, приемом описанным выше
3. Внутри функции потока событие объекта устанавливается в сигнальное состояние
4. Функция потока останавливается в ожидании внешнего события

Как правило, приведенная схема работает. Но когда потоков становится действительно много, иногда возникает ситуация, когда пункт 3 алгоритма приводит к ошибке обращения к памяти, при чем с помощью отладчика, удалось выяснить, что неверен не только объект внутреннего события, но и все остальные поля класса, из чего я делаю вывод, что неверен указатель this, относительно которого вызывается функция член класса.

В чем может быть реальная проблема в пункте 3?
this в конструторе
Re: Снова this в конструкторе
От: Caracrist https://1pwd.org/
Дата: 01.03.12 19:47
Оценка: 2 (1) +1
Здравствуйте, found, Вы писали:

F>Здравствуйте.


F>Читал в разных местах, что с одной стороны использование this в конструкторе — нормальная практика, с другой стороны нет.

F>Опишу задачу упрощенно: при конструировании объекта создать, связанный с ним поток, использующий некоторые поля объекта.
F>Пример:

F>
F>struct Foo
F>{
F>  Foo() : data(0)
F>  {
F>    CreateThread(..., Starter, this, ...);
F>  }

F>  static int Starter(void* obj)
F>  {
F>    static_cast<Foo*>(obj)->run();
F>  }

F>  void run()
F>  {
F>    m_Data++;
F>  }

F>  int m_Data;
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?


я думаю этот список легко дополнить... у тебя какой вариант?
~~~~~
~lol~~
~~~ Single Password Solution
Re: Снова this в конструкторе
От: MescalitoPeyot Украина  
Дата: 01.03.12 19:55
Оценка: 7 (4) +1
Здравствуйте, found, Вы писали:

F>В конструкторе необходимо запустить поток, в котором сигнализировать событием объекта и остановиться в ожидании внешнего события.


Крайне не рекомендую так делать. Нужен объект связанный с потоком — сделайте конструирование двухфазным, что-нибудь в этом духе:

class Some
{
  private:
    Some();
    void initThread();
  public:
    static shared_ptr<Some> Create();
};

shared_ptr<Some> Some::Create()
{
  shared_ptr<Some> result(new Some());
  result->initThread();
  return result;
}


Нетривиальные (особенно связанные с многопоточностью) операции в констркторе и деструкторе — путь к граблям.
Re[2]: Снова this в конструкторе
От: found Россия ak239.ru
Дата: 01.03.12 20:14
Оценка:
Здравствуйте, 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. При выносе функции запуска потока за пределы конструктора (сейчас использую это решение) ошибка пропадает. В данном случае возникает вопрос, решена ли сама проблема, либо же она только замаскирована.

Думаю просмотреть сейчас сам код, который генерирует компилятор, в поисках иных зацепок. Больше вариантов, к сожалению, нет.
Re[2]: Снова this в конструкторе
От: found Россия ak239.ru
Дата: 01.03.12 20:21
Оценка:
Здравствуйте, MescalitoPeyot, Вы писали:

MP>
MP>class Some
MP>{
MP>  private:
MP>    Some();
MP>    void initThread();
MP>  public:
MP>    static shared_ptr<Some> Create();
MP>};

MP>shared_ptr<Some> Some::Create()
MP>{
MP>  shared_ptr<Some> result(new Some());
MP>  result->initThread();
MP>  return result;
MP>}
MP>


Спасибо за направление, в котором смотреть. Сейчас делаю точно тоже самое, но значительно в менее красивой форме: за new следует вызов init.
Переделаю решение, используя ваш пример.
Re[3]: Снова this в конструкторе
От: Caracrist https://1pwd.org/
Дата: 01.03.12 20:33
Оценка:
Здравствуйте, found, Вы писали:

F>* В деструкторе объекта поток при необходимости завершается, время жизнь объекта > времени жизни потока

Как объект завершает поток? (надеюсь не TerminateThread...) У него есть хендл на него? А если не успеет записать хендл до вызова деструктора? Между созданием потока и возвращением из CreateThread может многое произойти...

F>* Сам вызов функции происходит в потоке следующим образом:

F>вход в критическую секцию, привязанную к конкретному объекту, вызов функции, которая сигнализирует событие, ожидание внешнего события, выход из критической секции.
Очень экзотические краши бывают при попытке зайти в невалидную критическую секцию...

F>* Класс ни от чего не наследуется и от него тоже ничего не наслудется

F>* Есть только static_cast при запуске потока, описанный в примере
F>* Дальше кода в конструкторе нет, предполагал данный вариант, поэтому переместил создание потока в самый конец
Это хорошо, деструкторы не в счёт.

F>Единственный нюанс этой теории: если вынести запуск потока в отдельную функцию-член класса, т.е. вместо: new Foo(...), писать

F>
F>Foo* obj = new Foo(...);
obj->>init();
F>


это кстати всегда хорошо


F>Думаю просмотреть сейчас сам код, который генерирует компилятор, в поисках иных зацепок. Больше вариантов, к сожалению, нет.
~~~~~
~lol~~
~~~ Single Password Solution
Re[4]: Снова this в конструкторе
От: found Россия ak239.ru
Дата: 01.03.12 20:55
Оценка:
Здравствуйте, Caracrist, Вы писали:

C>Как объект завершает поток? (надеюсь не TerminateThread...) У него есть хендл на него? А если не успеет записать хендл до вызова деструктора? Между созданием потока и возвращением из CreateThread может многое произойти...


В данный момент гарантируется тот факт, что объект не может быть уничтожен до завершения потока, логикой работы программы.
Раньше использовал TerminateThread, какие нюансы могут при этом возникать. Мои рассуждения, сводились к тому, что насильно уничтожается стек объекта, что может приводит к нежелательным последствиям. Может ли вызов этой функции влиять на другие потоки и кучу приложения?

F>>* Сам вызов функции происходит в потоке следующим образом:

F>>вход в критическую секцию, привязанную к конкретному объекту, вызов функции, которая сигнализирует событие, ожидание внешнего события, выход из критической секции.
C>Очень экзотические краши бывают при попытке зайти в невалидную критическую секцию...

Критическая секция инициализируется с помощью функции InitializeCriticalSection внутри конструктора до создания потока. Но, т.к. , все поля класса имеют невалидные значения в отладчике во время ошибки, то и критическая секция является неверной.

С утра внимательно просмотрю код, возможно, не вижу какой-то банальной ошибки. Либо просто, действительно, не стоит в принципе создавать поток внутри конструктора. Но т.к. проект учебный интересно докопаться до нюансов.
Re[5]: Снова this в конструкторе
От: Caracrist https://1pwd.org/
Дата: 01.03.12 21:00
Оценка:
Здравствуйте, found, Вы писали:

F>Раньше использовал TerminateThread, какие нюансы могут при этом возникать. Мои рассуждения, сводились к тому, что насильно уничтожается стек объекта, что может приводит к нежелательным последствиям. Может ли вызов этой функции влиять на другие потоки и кучу приложения?

Если поток в критической секции, TerminateThread делает её не валидной. Любая операция с CS может привезти крашу. Я уже молчу не выполненом dllmain и __declspec(thread).
~~~~~
~lol~~
~~~ Single Password Solution
Re: Снова this в конструкторе
От: __kot2  
Дата: 02.03.12 04:35
Оценка:
Здравствуйте, found, Вы писали:
F>Можно ли поступать подобным образом? Передавать this во внешнюю функцию, которая вызывает статическую функцию с этим параметром, далее относительно него запускать функцию член.
проблемы могут быть если конструктор Foo был вызван при конструировании кого-то наследованного от Foo.
тогда для виртуальных ф-ий еще будет неправильная таблица виртуальных методов, а если кто-то захочет откастить this dynamic_cast ом к наследнику Foo, я даже и не знаю, что будет.
Re: Снова this в конструкторе
От: MasterZiv СССР  
Дата: 02.03.12 09:23
Оценка:
> struct Foo
> {
> Foo() : data(0)
> {
> CreateThread(..., Starter,this, ...);
> }

> Можно ли поступать подобным образом? Передавать this во внешнюю функцию, которая

> вызывает статическую функцию с этим параметром, далее относительно него
> запускать функцию член.

Можно.
Только убедись, что этот объект в этой функции не будет использован до момента
полного окончания его инициализации.

В частности, если

CreateThread(..., Starter,this, ...);

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

Если поток сразу же пускается, и, например, если ещё наследник Foo,
который ещё не завершил выполнение своего конструктора, то будут гонки.
Posted via RSDN NNTP Server 2.1 beta
Re: Снова this в конструкторе
От: kjam Украина  
Дата: 02.03.12 09:57
Оценка:
Здравствуйте, found, Вы писали:

F>Читал в разных местах, что с одной стороны использование this в конструкторе — нормальная практика, с другой стороны нет.

F>Опишу задачу упрощенно: при конструировании объекта создать, связанный с ним поток, использующий некоторые поля объекта.

Я в подобных случаях использую pimpl, как-то так:
struct Foo
{
    Foo(bool ok) : m_impl(new Impl())
    {
        m_thread.reset(new Thread(..., m_impl, &Impl::run, ...));

        if (ok)
        {
             throw Something();
        }
    }
private:

    class Impl
    {
    public:
        Impl() : m_Data(0) {}
  
        void run()
        {
            m_Data++;
        }
    private:
        int m_Data;
    };

    boost::shared_ptr<Impl> m_impl;
    boost::shared_ptr<Thread> m_thread;
};
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.