Что происходит, когда я создаю новый поток из .NET?

Я хочу понять, что именно происходит за сценой, когда я создаю новый поток в .NET, что-то вроде этого:

Thread t = new Thread(DoWork); //I am not interested in DoWork per se
t.Start();

1. Какие связанные с потоком объекты создаются в CLR и ядре Windows?
2. Почему эти объекты нужны?
3. Сколько управляемой/неуправляемой памяти (кучи и стека) выделено на x86, x64 Windows?

UPDATE
Я ищу такие объекты, как управляемый объект потока, который я предполагаю t, но, возможно, некоторые другие дополнительные управляемые объекты; объект потока ядра, блок среды пользовательских потоков и т.д.

Большое спасибо!

Ответы

Ответ 1

выделенная память Win32 и ядра

Я не совсем уверен, как работает .NET-часть, но если среда выполнения решит создать реальный поток с ОС, она в конечном итоге вызовет Win32 API CreateThread в kernel32.dll, возможно, из mscorlib.ni.dll

По умолчанию новые потоки получают 1 Мбайт виртуального адреса для стека, который фиксируется по мере необходимости. Это можно контролировать с помощью параметра maxStackSize. Размер стека основного потока исходит из параметра самого исполняемого файла.

В адресном пространстве процесса будет выделен TEB (блок среды потока) (см. также). Кстати, регистр FS на x86 указывает на это для таких вещей, как локальное хранилище потоков и обработка структурированных исключений (SEH). Вероятно, есть другие вещи, выделенные Win32, которые не документированы.

При создании потока Win32 связывается процесс сервера Win32 (csrss.exe). Вы можете видеть, что csrss имеет дескрипторы, открытые для всех процессов и потоков Win32 в Process Explorer для какой-то бухгалтерии.

DLL, загруженные в процесс, будут уведомлены о новом потоке и могут выделять собственную память для отслеживания потока.

Ядро создаст объект ETHREAD [макет] (полученный от KTHREAD) из ядра non-paged pool для отслеживания состояния потока. Также будет выделен стек ядра (по умолчанию значение 12k для x86), которое может быть выгружено (если поток не находится в состоянии ожидания режима ядра).

Почему так много вещей нужно выделить память для потока

Потоки - это наименьшая предустановленная единица, которую предоставляет ОС, и с ними связано много контекста. Многим различным компонентам необходимо предоставить отдельный контекст для каждого потока, поскольку системные службы должны иметь возможность обрабатывать несколько потоков, выполняющих разные вещи в одно и то же время.

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

Сколько выделено памяти

Трудно сказать, сколько памяти выделено для потока, поскольку оно распространяется по нескольким адресным пространствам и кучам. Он будет варьироваться в зависимости от версий Windows, установленных компонентов и того, что загружено в текущий процесс.

Самая большая стоимость обычно считается 1 МБ адресного пространства, используемого по умолчанию для новых потоков, но даже этот предел позволяет многосот использовать в одном процессе без пробега.

Если в проекте используется гораздо больше потоков ОС, чем количество процессоров в системе, его следует пересмотреть. Рабочие очереди с пулом потоков и легкими потоками с планированием режима пользователя с помощью волокон или другой реализации библиотеки должны иметь возможность обрабатывать многопоточность, не требуя чрезмерного количества потоков ОС, делая стоимость памяти для потоков неважной.

Ответ 2

Итак, это действительно сложный вопрос, который на самом деле не имеет большого ответа от "x".

  • CLR не требуется отображать один поток CLR на одно OS-волокно. Итак... это трудно ответить. Я думаю, что текущая версия .NET(4.0) пытается использовать связь 1-к-1 между потоками CLR и волокнами ОС, когда это возможно на всех ОС. Предыдущие версии .NET(более похоже на <= 1.1) Я не уверен, что это было во всех ОС. Планировщик обрабатывает большинство этих объектов, и они не будут частью графического объекта .NET. Этот планировщик является частью CLR, а не частью объекта Thread. Если вы копаете в IL, вы увидите много внутренних вызовов для фактического выполнения.
  • Я предполагаю, что вопрос: "Почему эти объекты нужны?" Если это так, то из-за того, что хост ОС должен иметь волокно для выполнения кода для этого потока на нем. Использование ThreadPool может значительно снизить затраты на их создание каждый раз.
  • Извините... зависит. Многие из них неуправляемы, что означает, что хост ОС может решить эту проблему по-разному в зависимости от загрузки и версии системы.

"Логическая абстракция потока управления захватывается экземпляром объекта System.Threading.Thread в библиотеке классов." http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf

Таким образом, стандарт EMCA действительно ничего не говорит о теме. Но, к счастью, у нас есть...

"Поскольку объект потока CLR является для каждого волокна, любая информация, зависающая от него, также является волокном. Thread.ManagedThreadId возвращает стабильный идентификатор, который обтекает поток CLR. Он не зависит от идентичности физический поток ОС, что означает, что использование его не подразумевает никакой сродства. Различные волокна, работающие на одной и той же нити, возвращают разные идентификаторы". От Джо Даффи http://www.bluebytesoftware.com/blog/2006/11/10/FibersAndTheCLR.aspx

Ответ 3

Посмотрите здесь; существует сопоставление между управляемыми (то есть CLR) примитивами и неуправляемыми (то есть ядром NT), которые могут отвечать на большинство ваших вопросов.