Как правильно определить основной (первый) поток процесса

Было много вопросов о том, как определить основную нить процесса. Распространенным ответом было то, что «главного» потока не существует и все потоки эквивалентны.

Это неверный и весьма теоретический ответ. Основной поток — это первый поток, создаваемый процессом. Отладчик Visual Studio в окне «Потоки» может каким-то образом отличить основной поток от остальных. И я хочу знать во время загрузки статически связанных DLL (до WinMain), как это делает отладчик. В конечном итоге я хочу получить идентификатор этой темы.

Обновлено: Как заявил @IInspectable: «Хотя существует поток, который в конечном итоге инициализирует процесс, этот поток не имеет каких-либо отличительных особенностей. Он может завершиться в любой момент, в то время как другие потоки, принадлежащие этому процессу, продолжают выполняться». Я это знаю и полностью с этим согласен. Но я имею дело с гигантским продуктом 30-летней давности, состоящим из нескольких взаимодействующих EXE-файлов и нескольких сотен DLL. И многие разные библиотеки DLL (которые ничего не знают друг о друге и о EXE-файлах) имеют фрагменты кода, которые разрешено запускать только в основном потоке. В противном случае приложение выйдет из строя. Можно много раз повторять, что это очень плохой дизайн, но это то, что у меня есть и я ничего не могу с этим поделать. И это то, что я называю «практикой», в отличие от абстрактно истинных, но бесполезных утверждений.

Следовательно, нашему продукту нужен своего рода сервис, который сообщает, является ли текущий поток основным потоком процесса. Поэтому я выбрал очень простую DLL, которая статически связана со всеми EXE-файлами и почти со всеми другими DLL, и реализовал там такой «сервис» точно так, как описано в ответе @MSalters.

Это работало хорошо, пока недавно я не начал получать жалобы от разработчиков из офиса в другой стране о том, что выбранная DLL загружается в рабочий поток (что приводит к сбою приложения). Мы не можем воспроизвести это явление в нашем офисе, поэтому все, что у меня есть, — это снимки окна потока отладчика. Ниже приведен один из них; если это кому-то что-то скажет, я был бы очень рад узнать.

Ну, что это? Неправильно? или «очень теоретический»? Вы не можете иметь и то, и другое.

user207421 10.05.2024 10:21

Дубликат: stackoverflow.com/questions/4891303/…

MSalters 10.05.2024 10:48

@user207421 user207421 Я знаю, что все потоки имеют одинаковую внутреннюю структуру и одинаково обрабатываются ОС. Так что с теоретической точки зрения они неотличимы. Но когда дело доходит до использования потоков, основной поток часто бывает особенным. В нашем приложении, которое содержит, вероятно, 15 миллионов строк кода C++ и C#, некоторые части могут выполняться только в основном потоке. Не спрашивайте меня, почему – это факт. Я также хотел бы знать, почему мой вопрос был отклонен.

Ilia 10.05.2024 10:55

Правильный ответ: «Основной темы нет». Хотя существует поток, который в конечном итоге инициализирует процесс, этот поток не имеет каких-либо отличительных особенностей. Он может завершиться в любой момент, пока другие потоки, принадлежащие этому процессу, продолжают выполняться. Более актуальный вопрос здесь: зачем вам это знать? Что вы планируете делать с этой информацией?

IInspectable 10.05.2024 11:02

Это неверно.. - Ваше утверждение неверно. что тебе действительно нужно сделать?

RbMm 10.05.2024 11:11

@Илия, ты не ответил на мой вопрос.

user207421 10.05.2024 11:43

@IInspectable Пожалуйста, прочитайте мой предыдущий комментарий, чтобы понять, зачем мне нужен идентификатор основного потока. И мне это нужно внутри очень глубокого слоя кода, который ничего не знает, скажем, о главном окне, из которого я мог бы получить такую ​​информацию. Внутри DLL, которая используется в нескольких разных EXE-файлах. Поэтому я хотел инкапсулировать эту информацию в эту DLL. И не говорите мне о плохом дизайне — невозможно перепроектировать приложение, имеющее 15 миллионов строк кода и созданное более 30 лет назад.

Ilia 10.05.2024 11:47

@RbMm Пожалуйста, посмотрите мой ответ на IInspectable.

Ilia 10.05.2024 11:57

@user207421 user207421 Думаю, да. Я спрашиваю не из теоретического интереса. Мне нужно решить практическую задачу, и я ограничен в решениях, которые могу использовать.

Ilia 10.05.2024 12:11

@Ilia, вы можете вызвать NtGetNextThread, чтобы получить дескриптор текущего первого потока в вашем процессе. или используйте NtQuerySystemInformation с SystemExtendedProcessInformation или SystemProcessInformation и получите список идентификаторов потоков в вашем процессе. в вашем приложении — первый поток и будет «основным». несмотря на то, что это действительно неправильный дизайн. dll — это пассивный код. он экспортирует функции и интерфейсы. и экспортированный код, вызываемый другим кодом, в потоках, которые выбираете не вы. dll обычно не заботится о том, в каком потоке выполняется код, и не выбирает это.

RbMm 10.05.2024 12:27

Мне нужно решить практическую задачу - лучше вы спросите об этой проблеме, а не в "основной" теме.

RbMm 10.05.2024 12:29

«Как мне определить конкретную нить?» это вопрос, отличный от вопроса «Как найти основной поток процесса?». Вы просили последнее, но, видимо, вам нужно первое.

IInspectable 10.05.2024 15:53

Судя по вашему снимку экрана, некоторый код в вашем процессе вызывает LoadLibraryW из других потоков. Не системный код, а «ваш» код. Или, может быть, некоторые компоненты в системе (например, av, edr) внедряют библиотеки DLL в ваш код через CreateRemoteThread + LoadLibraryW.

RbMm 10.05.2024 19:32

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

Ilia 10.05.2024 20:28

Я почти уверен, что DLL не заботится ни на йоту о «основном» потоке - в этом нет ничего особенного. Однако их, вероятно, волнует поток графического интерфейса, который является особенным. Или, что еще хуже, потоки графического интерфейса, учитывая кажущийся беспорядок.

MSalters 10.05.2024 21:23

Вы не ответили на мой вопрос, имеете ли вы в виду «неверно» или «очень теоретически». Вы не можете поддерживать оба этих убеждения одновременно в логическом уме. Они взаимно противоречивы.

user207421 11.05.2024 01:46

@user207421 user207421 Это правда, что я не могу предпочесть одну тему другой, потому что они неотличимы. Но верно также и то, что существует поток, известный как основной, который создается процессом первым. Те, кто говорит, что главного потока нет, на самом деле хотят навязать мне свою веру в то, что в правильно спроектированной программе код не должен зависеть от того, в каком потоке он выполняется. И они правы. Но это не помогает решить мою проблему. Реальная жизнь сложнее двоичной логики.

Ilia 11.05.2024 22:31

@Илья, ты вообще смотришь на свой скриншот? темы (2) со стартовым адресом LoadLibraryWStub - не дает вам идей? это какой-то внешний вызов процесса CreateRemoteThread с LoadLibraryW в качестве точки входа. классическая инъекция dll.

RbMm 12.05.2024 14:11

@MSalters Вы правы, существует много потоков графического интерфейса. Часть из них использует MFC, часть — WTL, третья часть — WPF. Как я уже сказал, продукт старый и огромный, поэтому такой микс разных технологий. Теоретически я мог бы получить основной поток из главного окна приложения, но это часто необходимо в библиотеках DLL, не имеющих доступа к главному окну. Мне нужно было подтверждение того, что моя реализация, которая фактически соответствует вашему ответу, верна.

Ilia 14.05.2024 13:12

@Ilia: Что заставляет вас думать, что «основной поток MFC» совпадает с «основным потоком WTL», либо совпадает с «основным потоком WPF», либо что любой из трех является первым потоком в процессе ? Кроме того, то же ошибочное предположение: в Win32 нет понятия главного окна приложения. И первое окно часто представляет собой заставку, поскольку основное приложение все еще загружается.

MSalters 14.05.2024 13:16

@MSalters Не интересно, что может быть, интересно только то, что у меня есть. У меня есть главное окно приложения, созданное в WinWain. Не важно, что главное окно не первое (из-за заставки). Имеет значение только то, что главное окно принадлежит первому потоку процесса, который обычно называют «основным» потоком. Это характерно для большинства приложений Windows, по крайней мере, для достаточно старых, и лучше это учитывать при ответе.

Ilia 14.05.2024 15:19

@Ilia: Потоки Win32 — это вещь Kernel32. Kernel32 не зависит от User32, где находится CreateWindow. Поэтому вы не можете вызвать какую-либо функцию Kernel32, чтобы получить статус потока, и это зависит от User32. И User32.dll не такой уж особенный, он тоже загружается, как и другие DLL (в отличие от Kernel32, который является особенным и гарантированно загружается раньше вашего DllMain).

MSalters 14.05.2024 15:43

@MSalters «Основной поток MFC»… «Основной поток WTL»… «Основной поток WPF» — таких понятий не существует. Существует понятие основного потока приложения — первого, созданного в процессе и выполняющего точку входа WinMain. Этот поток вполне может быть без графического интерфейса. Я тоже не понял, что вы имели в виду в своем последнем комментарии. Конечно, я не вызываю CreateWindow или другие функции, специфичные для User32, изнутри DllMain.

Ilia 22.05.2024 09:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
23
120
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Отладчик может показать, какой поток (если таковой имеется) возник из точки входа EXE. Это не WinMain, потому что вашему компилятору нужна точка входа для настройки среды выполнения. Эта среда выполнения вызывает ваш WinMain.

Во время загрузки DLL (т. е. DllMain) ваш код выполняется под блокировкой загрузчика, и в это время вы мало что можете сделать. Но вызов DLL_PROCESS_ATTACH происходит именно в этом первом потоке. Так что вам не нужно ничего делать, чтобы узнать эту ветку. Вы можете смело использовать TlsAlloc, чтобы отметить эту тему для дальнейшего использования. Вы также можете смело вызывать GetCurrentThreadId, поскольку это функция kernel32, которая уже загружена до DllMain.

Но вызов DLL_PROCESS_ATTACH происходит именно в этом первом потоке. - DLL_PROCESS_ATTACH конкретно происходит в потоке, который вызывает LoadLibrary[Ex] (LdrLoadDll) - и это может быть любой поток. в случае, если это связано с зависимостями exe dll - да, «первый» поток

RbMm 10.05.2024 11:25

@MSalters О, слава богу, я получил один содержательный ответ, а не просто отрицательные голоса. Ваш совет был именно тем, что я изначально реализовал, и он работал хорошо до недавнего времени, когда некоторые разработчики (как ни странно, а не конечные пользователи) начали жаловаться, что на их машинах (статически связанная) DLL загружается в рабочий поток. Просматривая Интернет, я обнаружил, что поведение загрузчика было изменено в Windows 10, так что библиотеки DLL теперь можно загружать в потоки из пула потоков. Это основная причина моего вопроса.

Ilia 10.05.2024 13:17

@Ilia в Windows 10 ничего не меняется. Точка входа в Dll всегда вызывается из потока, который начинает загрузку dll.

RbMm 10.05.2024 13:42

@RbMm Я был удивлен, услышав это от вас, недавно увидев ваш ответ на вопрос «Почему при запуске консольного приложения Win32 появляются три неожиданных рабочих потока?», а также прочитав этот блог.

Ilia 10.05.2024 14:25

@RbMm Я знаю, что ты эксперт; Я просто запутался, потому что прочитал два поста о «параллельной загрузке» в Windows 10, и один из этих постов — ваш собственный. Почему ты обижаешься вместо того, чтобы объяснить, чего мне не хватает?

Ilia 10.05.2024 15:15

@Ilia DLL_PROCESS_ATTACH всегда вызывается из потока, который начинает загрузку dll (прямую или косвенную). и параллельный загрузчик здесь ничего не меняет. так что в win 10 и здесь ничего не изменилось. ответ неверен. Но вызов DLL_PROCESS_ATTACH происходит именно в этом первом потоке. - об этом я говорю в первом комментарии.

RbMm 10.05.2024 15:29

@RbMm «всегда вызывается из потока, который начинает загрузку dll» - но является ли этот поток основным (первым) потоком процесса? И если это так, то что такое параллельный загрузчик? Я говорю здесь о DLL, которая загружается из таблицы импорта процесса, никакого вызова LoadLibrary нет.

Ilia 10.05.2024 16:00

@RbMm: Обратите внимание, что в вопросе упоминаются DLL, которые загружаются при запуске программы, то есть не через LoadLibrary (или отложенную загрузку). В документации DllMain конкретно указано, что «Этот вызов [т. е. DllMain] выполняется в контексте потока, который вызвал изменение адресного пространства процесса, например, основного потока процесса или потока, вызвавшего LoadLibrary.». И последний вариант не применим.

MSalters 10.05.2024 16:02

@Ilia - как я говорю - DLL_PROCESS_ATTACH всегда вызывается из потока, который начинает загрузку dll (прямую или косвенную). dll можно начать загружать в любом потоке. параллельный загрузчик - выполняет некоторую часть задания загрузки dll в рабочих потоках (загружает дополнительные dl, одновременно разрешая импорт), но DLL_PROCESS_ATTACH всегда вызывается в начальном потоке. DLL, которая загружается из таблицы импорта процесса - для этой библиотеки DLL_PROCESS_ATTACH всегда вызывается поток, который затем вызывает точку входа exe ( WinMain или main, если хотите)

RbMm 10.05.2024 16:04

@Ilia DLL, которые загружаются при запуске программы - для этих DLL DLL_PROCESS_ATTACH всегда вызывается в вашем «основном» потоке. и параллельный загрузчик здесь в любом случае не реализован

RbMm 10.05.2024 16:06

@Ilia - в вопросе упоминаются библиотеки DLL, которые загружаются при запуске программы - против И я хочу знать, что время загрузки DLL (до WinMain) не самое лучшее, как вы это говорите (до WinMain). в этом случае вы действительно можете просто сохранить идентификатор текущего потока. но очень интересно для чего? вы позже планируете использовать это из dll

RbMm 10.05.2024 16:09

@RbMm Я объяснил причину и описал реальную проблему при редактировании моего вопроса. Буду признателен, если посмотрите. Я также попросил возобновить вопрос.

Ilia 10.05.2024 20:45

@Илья, я не вижу, где ты описываешь реальную проблему.

RbMm 10.05.2024 21:05

Другие вопросы по теме