Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > Использование CRT в CallBack. Чужой поток.


Автор: Alexeis 15.11.2010, 14:22
Насколько я знаю, для правильного функционирования CRT в потоках требуется инициализация потока при помощи _beginthread(ex) .
Т.е. дополнительно создается вот такая структура, которая хранит все глобальные переменные, которые использует CRT. 
Код

typedef struct _thread_data
{
    struct _thread_data *thread_link; /* next struct on free list */
    void    *thread_arglist;        /* argument list to pass to thread_func */
    HANDLE  thread_handle;          /* handle of thread object */
    int     thread_errno;           /* errno */
    int     thread_doserrno;        /* _doserrno */
    void    (_USERENTRY *thread_func)(void *); /* thread starting address */
    char    *thread_token;          /* pointer to next strtok() token */
    char    *thread_template;       /* pointer to temp filename template */
    int     thread_mbshift;         /* shift state for mbtowc() */
    int     thread_wcshift;         /* shift state for wctomb() */
    void    (_USERENTRY **thread_sig)();/* pointer to signal table */
    void    *thread_excep;          /* exception registration pointer */
    void    *thread_time;           /* data used by time functions */
    void    *thread_cvt;            /* array used by fcvt() and ecvt() */
    void    *thread_strbuf;         /* array used by strerror() */
    void    *thread_wstrbuf;        /* array used by wcserror() */
    void    *thread_passbuf;        /* array used by getpass() */
    void    *thread_pathbuf;        /* array used by searchpath() */
    __seed_t   thread_seed;         /* random number seed */
    void    *thread_exceptvars;
#if defined(_MBCS)
    int     thread_lead_byte;       /* pending leadbyte data used by putch() */
#endif
    int     thread_ex_mode;         /* 1 means _beginthreadex was called */
    int     thread_xtra;            /* extra place holder. unused for now */
} THREAD_DATA;


Теперь ситуация. Функция вызывается калбеком из чужого модуля (из дополнительного потока), который, не вызывал нужной _beginthread (каждый CRT, как я понимаю, требует свой _beginthread). К чему приведет в этом случае использование функций CRT? 

Автор: Sartorius 15.11.2010, 14:32
ИМХО если функции не используют общих объектов, то все Ок будет. Если используют (по комментам видно, что это за функции) то могут сработать некорректно конкурируя за данные или пытаясь посмотреть CRT контекст потока. 

Автор: Alexeis 15.11.2010, 14:56
Цитата(Sartorius @  15.11.2010,  15:32 Найти цитируемый пост)
Если используют (по комментам видно, что это за функции) то могут сработать некорректно конкурируя за данные или пытаясь посмотреть CRT контекст потока. 

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

Автор: ama_kid 15.11.2010, 15:19
Цитата(Alexeis @  15.11.2010,  15:22 Найти цитируемый пост)
К чему приведет в этом случае использование функций CRT?

Ну, видимо, к тому, от чего призвана защитить функция _beginthreadex: всякие внутренние СRT-шные переменные типа errno,_doserrno, strtok, _wcstok, strerror и легион им подобных могут начать показывать неактуальные значения, что может привести к неуловимым глюкам, зависящим от расположения звёзд в созвездии Плеяды.

Автор: Sartorius 15.11.2010, 15:28
Alexeis, а откуда ты beginthread взял этот? Используй CreateThread
Посмотри исходники CRT. Там всякие strtok-и вызывают первым делом _getpdt. ptd создает контекст потока, если его еще нет. Все будет Ок, CRT не дураки писали.

ЗЫ
 В общем никогда не встречал проблем с вызовом CRT функций из любых потоков.

Автор: Alexeis 15.11.2010, 16:08
Цитата(Sartorius @  15.11.2010,  16:28 Найти цитируемый пост)
Alexeis, а откуда ты beginthread взял этот? Используй CreateThread

  MSDN рекомендует обратное.
  
Цитата(Sartorius @  15.11.2010,  16:28 Найти цитируемый пост)
Посмотри исходники CRT. Там всякие strtok-и вызывают первым делом _getpdt. ptd создает контекст потока, если его еще нет.

  Проверил. Действительно, и билдер и студия создают блок переменных потока по первой необходимости. Единственная проблема, это утечка памяти.

  Вероятно следует вызвать _freeptd() по окончанию пользования этим потоком, иначе ведь возникнет утечка памяти. 

Автор: xvr 15.11.2010, 16:11
Вот что написано в MSDN по поводу CreateProcess:
Цитата

A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex and _endthreadex functions for thread management rather than CreateThread and ExitThread; this requires the use of the multi-threaded version of the CRT. If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.
Раньше еще было написано, что использование CreateProcess вместо _beginthread* может привести к утечкам памяти

Автор: Alexeis 15.11.2010, 16:23
Цитата(xvr @  15.11.2010,  17:11 Найти цитируемый пост)
If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.

  Насколько я понял, проблема еще в том, что любое аппаратное исключение типа AV или деление на 0 приведет к краху, поскольку SEH фрейм не будет создан.
  Кроме того, оказывается следует вызывать не _freeptd() , а специально созданную _endthreadex() .

Добавлено через 4 минуты и 11 секунд
P.S. в моей ситуации SEH фрейм создаст билдер (точнее библиотеки delphi), так что этой проблемы не будет. С другой стороны будут созданы 2е копии переменных. Одна для CRT билдера, другая для CRT студии. Билдер свою почистит, а ту что создаст студия придется очищать руками.

Автор: Sartorius 15.11.2010, 16:36
Цитата(Alexeis @  15.11.2010,  17:23 Найти цитируемый пост)
Насколько я понял, проблема еще в том, что любое аппаратное исключение типа AV или деление на 0 приведет к краху, поскольку SEH фрейм не будет создан

Не совсем понял. Это почему? Любой поток содержит SEH list. Вроде ничего не мешает использвать SEH исключения

Автор: Alexeis 15.11.2010, 16:44
Sartorius, цитирую рихтера
Цитата

Как это ни фантастично, но Ваш поток будет работать почти без глюков. Хотя
некоторые проблемы все же появятся. Во-первых, если этот поток воспользуется биб-
лиотечной функцией signal, весь процесс завершится, так как SEH-фрейм не подго-
товлен. Во-вторых, если поток завершится, не вызвав _endthreadex, его блок данных
не высвободится и произойдет утечка памяти.

  Рихтер обманывать не будет. 

Автор: GremlinProg 16.11.2010, 07:47
в принципе, нам же ни кто не мешает правильно инициализировать поток CRT:
Код

static void _callthreadstartex(void)
{
    _ptiddata ptd;           /* pointer to thread's _tiddata struct */

    /* must always exist at this point */
    ptd = _getptd();

    /*
        * Guard call to user code with a _try - _except statement to
        * implement runtime errors and signal support
        */
    __try {
            _endthreadex (
                ( (unsigned (__CLR_OR_STD_CALL *)(void *))(((_ptiddata)ptd)->_initaddr) )
                ( ((_ptiddata)ptd)->_initarg ) ) ;
    }
    __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
    {
            /*
                * Should never reach here
                */
            _exit( GetExceptionCode() );

    } /* end of _try - _except */

}

это код из threadex.c,
_initaddr - это и есть сама процедура потока

но смотреть надо конечно не _callthreadstartex, а _threadstartex, в которой и происходит инициализация переменных

а приоткрыть внутренние функции CRT не так и сложно, достаточно их просто объявить

Автор: Alexeis 16.11.2010, 11:13
GremlinProg, такой код неприменим касательно в контексте CallBack вызовов. Как уже сказано выше, _getptd() и так вызывается по первому запросу. А блок __try{}__except(){} придется вставлять в каждую из функций обратного вызова, которые будут вызваны из дополнительного потока. Один такой блок можно организовать только внутри функции потока, а она физически расположена с другом модуле. Но это работать все равно не будет, и вот почему
функция _beginthread производит инициализацию SEH для каждого потока. Без этого __try{}__except(){} будет работать неправильно. 
Смотрим фрагмент
Код

    /* Store the thread's stack base in the thread local storage.
     */
    VirtualQuery((void *)&info, &info, sizeof(info));
    _stkindex = ((DWORD)info.AllocationBase + extra);

    _ExceptInit(t->thread_exceptvars);

    /* Set up the RTL exception handler.  Save a pointer to
     * its registration record in the thread data, so that
     * _endthread() can remove it.
     */
    _setexc(&hand);
    t->thread_excep = &hand;

    /* Reset the fpu control word since the OS has probably changed it on us */
    _fpreset();



Выводы следующие.
1) Вызовы CRT безопасны в функциях обратного вызова вызванных из дополнительного потока, даже если поток не создавался с _beginthread(ex).
2) Вызовы CRT в случае №1 могут приводить к утечке ресурсов, что впрочем решается дополнительным вызовом _endthreadex(), который явно освобождает ресурсы .
3) Вызовы CRT в случае №1, также не могут использовать функцию signal / __try{}__except(){}, по крайней мере до тех пор пока SEH не будет инициализирован явно.

Автор: GremlinProg 16.11.2010, 12:05
Цитата(Alexeis @  16.11.2010,  13:13 Найти цитируемый пост)
А блок __try{}__except(){} придется вставлять в каждую из функций обратного вызова, которые будут вызваны из дополнительного потока

ну конечно, в этом и смысл: завернуть работу CRT в твоем плагине в SEH со стандартным фильтром CRT: _XcptFilter,

ничего криминального тут нет: хочешь CRT - оберни исключения в CRT-фильтр
хочешь еще и переменные CRT - инициируй поток, как в _threadstartex, это и есть отправная точка любого CRT-потока,
и не важно, что ты это сделаешь только в функции своей библиотеки
Цитата(Alexeis @  16.11.2010,  13:13 Найти цитируемый пост)

Но это работать все равно не будет, и вот почему
функция _beginthread производит инициализацию SEH для каждого потока. Без этого __try{}__except(){} будет работать неправильно.

SEH организует _callthreadstartex в самом потоке, я же его показал,
а _beginthread производит инициализацию _ptiddata, которую подчищает _endthreadex
Цитата(Alexeis @  16.11.2010,  13:13 Найти цитируемый пост)
Смотрим фрагмент

угу, смотрим, к чему он?

Автор: Alexeis 16.11.2010, 12:28
Цитата(GremlinProg @  16.11.2010,  13:05 Найти цитируемый пост)
угу, смотрим, к чему он? 

  Это код аналогичный _threadstartex, но из билдера.
Смутила фраза
Цитата(GremlinProg @  16.11.2010,  08:47 Найти цитируемый пост)
в принципе, нам же ни кто не мешает правильно инициализировать поток CRT:

  И дальше идет код претендующий на полноту инициализации потока, хотя таковым не является. Полная инициализация потребует вызовов из _threadstartex и из _callthreadstartex. Причем, вероятно этот код будет непереносим на последующие версии студии. А это нехорошо. Лучше обойтись документированными средствами.

Автор: GremlinProg 16.11.2010, 13:30
Цитата(Alexeis @  16.11.2010,  14:28 Найти цитируемый пост)
И дальше идет код претендующий на полноту инициализации потока, хотя таковым не является.

это был ответ на твою последнюю цитату,
ты просто сам зачем-то разделил понятия SEH с __try-__except,
хотя последнее как раз этот SEH и ставит, вот я и сделал на нем акцент, раз уж тебе важно было использовать signal
Цитата(Alexeis @  16.11.2010,  14:28 Найти цитируемый пост)
Полная инициализация потребует вызовов из _threadstartex и из _callthreadstartex

именно это я и отметил
Цитата(GremlinProg @  16.11.2010,  09:47 Найти цитируемый пост)
но смотреть надо конечно не _callthreadstartex, а _threadstartex, в которой и происходит инициализация переменных

был смысл придираться?
Цитата(Alexeis @  16.11.2010,  14:28 Найти цитируемый пост)
Это код аналогичный _threadstartex, но из билдера.

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

RTL exception handler - это видимо подразумевается все таки простая функция, но не SEH (билдер не использую, точнее не скажу)

Автор: Alexeis 16.11.2010, 14:11
Цитата(GremlinProg @  16.11.2010,  14:30 Найти цитируемый пост)
ты просто сам зачем-то разделил понятия SEH с __try-__except,
хотя последнее как раз этот SEH и ставит, вот я и сделал на нем акцент, раз уж тебе важно было использовать signal

  Как я понял, добавление блока __try-__except, это не тоже самое, что инициализация SEH для потока. В этом и непонятка. Почему Рихтер пишет, что функция signal будет работать неверно? Или ей для работы не хватает пресловутого первого SEH фрейма?

Цитата(GremlinProg @  16.11.2010,  14:30 Найти цитируемый пост)
RTL exception handler - это видимо подразумевается все таки простая функция, но не SEH (билдер не использую, точнее не скажу) 
 Там он предположительно связывает обработку исключений в Dll и Exe

Автор: GremlinProg 16.11.2010, 14:51
Цитата(Alexeis @  16.11.2010,  16:11 Найти цитируемый пост)
Почему Рихтер пишет, что функция signal будет работать неверно? Или ей для работы не хватает пресловутого первого SEH фрейма?

да, потому что signal рождает исключение, перехватываемое фильтром CRT,
т.е. если фильтра нет, значит соответствующие для него колбеки не будут вызваны
Цитата(Alexeis @  16.11.2010,  16:11 Найти цитируемый пост)
Как я понял, добавление блока __try-__except, это не тоже самое, что инициализация SEH для потока

если бы это было так, то http://forum.vingrad.ru/index.php?showtopic=315070&view=findpost&p=2248213, между 12 и 13 строками было бы что-то, модифицирующее только что поставленный SEH, а там идет прямой вызов в пользовательскую процедуру потока

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)