На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Обратите внимание:
1. Прежде чем начать новую тему или отправить сообщение, убедитесь, что вы не нарушаете правил форума!
2. Обязательно воспользуйтесь поиском. Возможно, Ваш вопрос уже обсуждали. Полезные ссылки приведены ниже.
3. Темы с просьбой выполнить какую-либо работу за автора в этом разделе не обсуждаются.
4. Используйте теги [ code=cpp ] ...текст программы... [ /code ] для выделения текста программы подсветкой.
5. Помните, здесь телепатов нет. Старайтесь формулировать свой вопрос максимально грамотно и чётко: Как правильно задавать вопросы
6. Запрещено отвечать в темы месячной и более давности без веских на то причин.

Полезные ссылки:
user posted image FAQ Сайта (C++) user posted image FAQ Форума user posted image Наши Исходники user posted image Поиск по Разделу user posted image MSDN Library Online (Windows Driver Kit) user posted image Google

Ваше мнение о модераторах: user posted image B.V.
Модераторы: B.V.
  
> Непонятки с закрытием хендла запущенного потока
    В сетевом программировании в Винде очень популярна модель ввода-вывода
    порта завершения (IOCP).
    В этой модели есть очередь системных пакетов, которая обрабатывается рабочими потоками.
    Рабочие потоки управляются системой, используя их идентификаторы.
    В общем то вроде все понятно, но вот в коде основоположника этого дела Дж. Рихтера при создании потока есть такие две строчки:
    ExpandedWrap disabled
      ThreadHandle = CreateThread(NULL, 0,ServerWorkerThread, CompletionPort,0, &ThreadId);
      // Закрываем дескриптор потока
      CloseHandle(ThreadHandle);

    Грубо говоря, сразу же !!! после создания рабочего потока его хендл(дескриптор) удаляется.
    В сети есть несколько обсуждений этого кода - зачем закрывать хендл так никто предложить очевидное мнение не может.
    Неужели из-за того, что объект IOCP при своем управлении рабочими потоками использует их ThreadId?
    Кстати, параллельный вопрос - а если не закрыть то что будет???
    Сообщение отредактировано: Oleg2004 -
      А зачем объекту IOCP вообще что-то знать про эти потоки? У него есть семафор, ограничивающий число активных потоков, а сами хэндлы/id потоков не нужны.
        Цитата Oleg2004 @
        В сети есть несколько обсуждений этого кода - зачем закрывать хендл так никто предложить очевидное мнение не может

        Хэндл - это рукоятка.
        Он нужен, чтобы иметь возможность "по-рулить".
        Если хэндл потока будет закрыт, возможность по-управлять им
        будет утрачена, но поток не умрёт и будет продолжать работу.
        Этим он отличается от других хэндлов.
        Если хэндл вообще не закрывать, будет утечка ресурсов.

        Если поток уже не работает, а хэндл не закрыт,
        можно узнать результат работы потока посредством "GetExitCodeThread".
        Сообщение отредактировано: ЫукпШ -
          Вообще лучше использовать _beginthreadex или _beginthreadexex

          И поток может сам завершиться, кроме того можно посмотреть список потоков по ID процесса.

          А если хэндл не закрывать, то при завершении приложения оно завершится аварийно и захлопнет работающий поток.
            Цитата Pacific @
            А зачем объекту IOCP вообще что-то знать про эти потоки? У него есть семафор, ограничивающий число активных потоков, а сами хэндлы/id потоков не нужны.

            Объект IOCP манипулирует одной очередью (очередь ожидающих потоков) и двумя списками (список освобожденных потоков и список приостановленных потоков). Каждый элемент этих объектов представлен в нем в виде dwThreadId соответствующего потока. Поэтому без ThreadId потоков IOCP работать не может.

            Цитата ЫукпШ @
            Если хэндл потока будет закрыт, возможность по-управлять им будет утрачена, но поток не умрёт и будет продолжать работу.

            Как видно из вышесказанного мною (Рихтером :) ) возможность управления потоком вовсе не утеряна. Объект IOCP все о них знает и манипулирует ими в полном объеме.
            Цитата ЫукпШ @
            Если хэндл вообще не закрывать, будет утечка ресурсов.

            О каких ресурсах здесь идет речь?
            Вот типичный код рабочего потока, их может быть достаточно много (у PROGMAN'a аж 12)
            ExpandedWrap disabled
              DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
              {
              HANDLE CompletionPort = (HANDLE) CompletionPortID;
              DWORD BytesTransferred;
              LPOVERLAPPED Overlapped;
              LPPER_HANDLE_DATA PerHandleData;
              LPPER_IO_DATA PerIoData;
              DWORD SendBytes, RecvBytes;
              DWORD Flags;
                  while(TRUE)
                  {
              // Ожидаем завершения операции ввода/вывода  на любом сокете,
              // связанным с данным портом завершения
                if (GetQueuedCompletionStatus (CompletionPort, &BytesTransferred,
                 (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0)
                 {
                 // Сначала проверяем возврат на возможную ошибку.
                 printf ("GetQueuedCompletionStatus failed with error %d\n", GetLastError ());
                 return 0;
               }
              // Если произошла ошибка типа BytesTransferred=0, что свидетельствует о
              // закрытии сокета на удаленном хосте, закрываем свой сокет и очищаем данные,
              // связанные с сокетом
              if (BytesTransferred == 0 &&(PerIoData->OperationType == RECV_POSTED ││
                     PerIoData->OperationType == SEND_POSTED))
               {
               closesocket(PerHandleData->Socket);
               GlobalFree(PerHandleData);
               GlobalFree(PerIoData);
               continue;//Продолжаем цикл
               }
              // Обслуживаем завершенный запрос. Какая операция была закончена, определяем
              // по содержимому поля OperationTypefield в структуре PerIoData
              if (PerIoData->OperationType == RECV_POSTED)
              {
              // Если тип операции был помечен как WSARecv(), выполняем необходимые
              // действия с информацией, имеющейся в поле PerIoData->Buffer
              }
              // Выдаем следующий запрос на выполнение другой необходимой операции –
              // WSASend()или WSARecv(). В нашем случае это WSARecv() – мы продолжаем    
              //получать данные
              Flags = 0;
              //Формируем данные для следующего  вызова операции с перекрытием
              ZeroMemory(&(PerIoData->Overlapped),sizeof(OVERLAPPED));
              PerIoData->DataBuf.len = DATA_BUFSIZE;
              PerIoData->DataBuf.buf = PerIoData->Buffer;
              PerIoData->OperationType = RECV_POSTED;
              //Выполняем вызов WSARecv() и переходим опять к ожиданию завершения
              WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
               &(PerIoData->Overlapped), NULL);
                }//End While
              }//End ServerWorkerThread()

            Какие ресурсы будут утекать при исполнении такого кода с незакрытым хендлом?
            Цитата ter_nk_ @
            Вообще лучше использовать _beginthreadex или _beginthreadexex

            Рихтер обсуждает этот вопрос и не советует их применять.
              Одно дело сам поток, другое дело объект ядра, описывающий его. Ко всему прочему handle можно дублировать DuplicateHandle(), и это не приведёт к дублированию собственно потока исполнения. Поток может завершиться и благополучно почить, а объект будет жить, пока жива хотя бы одна ссылка на него, и такими ссылками являются как раз handle.
              Когда код создаёт поток исполнения, Oleg2004, ОСь отдаёт ему его handle, потому что если вдруг понадобится им управлять, то для этого нужен руль, этот самый handle. Обычно он нужен всегда, чтобы по меньшей мере отдать его WaitForXXX() для ожидания его завершения. И неважно, что поток к этому времени может уже завершиться, без живого объекта ядра, ссылающегося на него, узнать об этом было бы невозможно. _beginthread(), без -ex в конце, сама закрывала handle созданного потока, в результате было просто невозможно писать надёжные программы, т.к. в общем случае между созданием потока и возвратом их _beginthread() поток мог уже завершиться, и ни его handle, ни id уже не действительны, так что ссылку на созданный поток, чтобы отдать его WaitForXXX() или там GetExitCodeThread() получить просто уже неоткуда. Именно поэтому _beginthreadex() никогда сама не закрывает handle, это должен сделать сам программист, когда ему будет нужно.
              Ситуации, когда вызывающую сторону не интересует handle созданного потока, редки. Одна из них, если потоками владеет кто-то другой. С IOCP именно такой случай. Вызывающий код не будет заниматься этими потоками, это ему просто не нужно, ими рулит непосредственно IOCP. У него свои ссылки на них, и он сам с ними разберётся. Поэтому Рихтер и закрывает у себя ту ссылку, которую получил. Она его собственная, у IOCP свои собственные. IOCP закроет свои, когда настанет время, Рихтер закрывает свою, когда ему нужно, т.е. почти сразу. Если он этого не сделает, то у него будут течь ресурсы в виде элементов таблицы handles в его процессе, и рано или поздно она закончится и какой-нибудь безобидный fopen() вдруг провалится с чем-то вроде too many open files на ровном месте. Кроме того, каждый такой живой handle означает жизнь объекта ядра для давно умершего подсыльного объекта, таблица которых у ОСи тоже не резиновая, и подобные отказы начнут получать любые процессы, службы и драйверы на банальных CreateEvent(), EnterCriticalSection() итп, пока виновный процесс не будет завершён. Не весело. Особенно для приложений 24/7/365.

              Добавлено
              Цитата Oleg2004 @
              Рихтер обсуждает этот вопрос и не советует их применять.
              А не наоборот, часом? Для ОСи они не нужны, достаточно банальных CreateThread(), да. Однако эти потоки не в сферическом вакууме живут, а в приложении, написанном на C или там C++, а там вполне могут использоваться функции библиотеки языка. Прикол как раз в этих библиотеках. Многие функции RunTime Library языка C исторически не рассчитаны на многопоточность. Элементарно та же переменная errno или какие-нибудь функции strtok() или там asctime(). Таких функций не один десяток. Единственный способ сделать их многопоточными – распределять для них ресурсы локально для каждого исполнительного потока. _beginthreadex() именно это и делает: заблаговременно распределяет новую табличку с локальными ресурсами, критическими секциями и чем там ещё ей нужно, и где-то там у себя регистрирует, после чего запускает новый поток. Когда тот завершается, управление возвращается обратно в RTL, и та чистит за _beginthreadex(), удаляя более не нужные таблички и освобождая объекты ядра, выделенные для многопоточного обслуживания "плохо спроектированных" функций языка. Если создать поток в обход _beginthreadex(), то RTL не узнает о том, что в её ведении появился новый объект внимания. Нет, программа не свалится с access violation exception, все такие "неправильные" функции предварительно проверяют, а были ли выделены нужные ресурсы для них, и ежели нет, впопыхах делают то, что иначе сделала бы _beginthreadex(). Однако есть одно большое но: т.к. поток рождён неизвестно где и когда, RTL не знает, когда где он умирает, поэтому не может вовремя подчистить за впопыхами. И это реальная утечка ресурсов, т.к. таблички немаленькие и объекты ядра тоже не единичные.

              Добавлено
              Это не относится только лишь к таким потокам приложения, которые вообще не вызывают стандартных функций, и обходятся только нативным кодом и API. Чтобы написать такой поток, программисту нужно самому тщательно следить за руками, и даже при этом никто не даст гарантии, что через пару правок кода там не появится ненароком какой-нибудь snprintf()/OutputDebugString() или банальные new[]/delete[] под личиной std::vector<>.
              Сообщение отредактировано: Qraizer -
                Цитата Qraizer @
                Ситуации, когда вызывающую сторону не интересует handle созданного потока, редки. Одна из них, если потоками владеет кто-то другой. С IOCP именно такой случай. Вызывающий код не будет заниматься этими потоками, это ему просто не нужно, ими рулит непосредственно IOCP. У него свои ссылки на них, и он сам с ними разберётся. Поэтому Рихтер и закрывает у себя ту ссылку, которую получил. Она его собственная, у IOCP свои собственные. IOCP закроет свои, когда настанет время, Рихтер закрывает свою, когда ему нужно, т.е. почти сразу. Если он этого не сделает, то у него будут течь ресурсы в виде элементов таблицы handles в его процессе,

                Скорее всего дело именно так и обстоит.
                IOCP именно так и сделан, что ему для оперирования открытым рабочим потоком нужен только его Id. Хендлы никому просто не нужны в этом случае, к сожалению, об этом нигде открыто просто не говорят. Разработчики это знают, а для других это просто какой-то казус. Который вполне мы сейчас и разрешили.
                Цитата Qraizer @
                А не наоборот, часом?

                И так и не так... :)
                Вот что пишет Рихтер:
                Цитата
                В Microsoft не использовали типы данных Windows при создании прототипа функции _beginthreadex, чтобы избежать зависимости библиотеки времени исполнения C/C++ от ОС. Я одобряю такое решение, однако это усложняет применение _beginthreadex.
                С прототипом _beginthreadex связаны две проблемы. Во-первых, некоторые типы данных, используемые этой функцией, не соответствуют базовым типам в CreateThread.

                Цитата
                Вторая проблема — вариация первой. Функция _beginthreadex возвращает значение типа unsigned long, представляющее описатель вновь созданного потока. В приложениях желательно сохранять это возвращаемое значение в переменной типа HANDLE так:
                HANDLE hThread = _beginthreadex(...);
                Этот код заставит компилятор выдать предупреждение. Чтобы этого избежать, строку следует переписать так, чтобы согласовать типы:
                HANDLE hThread = (HANDLE) _beginthreadex(...);
                Но это не удобно. Чтобы немного облегчить жизнь, я определил в CmnHdr.h макрос chBEGINTHREADEX, который согласует типы

                Поэтому Рихтер и применяет в своих кодах CreateThread(). Меньше заморочек.
                Так что ответ на свой вопрос я получил.
                Что свидетельствует о высоком уровне наших форумских гуру и других форумчан.
                А это, как говорится, пустячок, а приятно :yes:
                  Цитата Oleg2004 @
                  Поэтому Рихтер и применяет в своих кодах CreateThread(). Меньше заморочек.

                  Эти заморочки начинаются в том случае, когда используется программирование на С.
                  ---
                  Напишем базовый класс-поток и поместим его в библиотеку.
                  Таким образом, вышеупомянутая конструкция появиться в исходных
                  текстах один единственный раз. И никогда не будет беспокоить
                  своим устрашающим видом.
                    Цитата Oleg2004 @
                    Поэтому Рихтер и применяет в своих кодах CreateThread(). Меньше заморочек.
                    ну, не только. Я его давненько читал, но насколько помню, в его коде нет функций языка, всё писано на голом API. Так что ему _beginthreadex() реально без надобности. Однако вряд ли можно ожидать, что другие программеры будут настолько аскетичны. Особенно, если будут писать на C++.
                    Сообщение отредактировано: Qraizer -
                      Цитата Qraizer @
                      Однако вряд ли можно ожидать, что другие программеры будут настолько аскетичны. Особенно, если будут писать на C++.
                      :D
                      1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                      0 пользователей:


                      Рейтинг@Mail.ru
                      [ Script execution time: 0,0372 ]   [ 15 queries used ]   [ Generated: 20.05.24, 18:13 GMT ]