Nano Hash - криптовалюты, майнинг, программирование

Как .NET Framework назначает идентификаторы потоков?

Мой вопрос связан с этим вопросом. Код в этом вопросе генерировал несколько потоков внутри цикла, и ОП там заметил, что идентификатор потока в их журнале, похоже, со временем увеличивается. Этот вопрос был о Java, но он заставил меня задуматься: как JVM и .NET Framework вообще назначают идентификаторы потоков? Меня особенно интересуют такие случаи, как OP, описанные в его сообщении (для проверки того, что потоки действительно создаются и уничтожаются, как и ожидалось), а также для получения дополнительного контекста о том, как интерпретировать журналы средств диагностики Visual Studio. Меня также интересует мое собственное понимание того, как работает структура.

Я спрашиваю здесь в первую очередь о .NET Framework, потому что, вероятно, было бы слишком широко спрашивать об обоих сразу (хотя я определенно был бы рад услышать подробности и о JVM). Но вот пример журнала, который я получал на вкладке «События» средств диагностики Visual Studio:

Вывод программы: поток 0x44c завершился с кодом 0 (0x0).

Вот несколько идентификаторов потоков по порядку:

0xcb0
0x2c4c
0x2c5c
0x1b10
0x1a60
0x27b4
0x2b80
0x2e04

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

Вот пример кода, который я использую:

await jobs.AsyncForEach(async delegate (Job job)
    {
       // Do some stuff, some of which involves async/await HttpClient calls to a RESTful API
    }, GlobalSettings.maxDegreeOfParallelism);

jobs имеет тип List<Job>, GlobalSettings.maxDegreeOfParallelism — это const int, указывающий максимальную степень параллелизма (из-за ограничения API от нашего поставщика), а AsyncForEach — это метод расширения для IEnumerable<T>:

public static async Task AsyncForEach<T>(this IEnumerable<T> enumerable, Func<T, Task> action, int degreeOfParallelism)
{
    List<Task> tasks = new List<Task>();

    foreach (T item in enumerable)
    {
        if (tasks.Count >= degreeOfParallelism)
        {
            await Task.WhenAny(tasks);

            tasks = tasks.Where(t => !t.IsCompleted).ToList();
        }

        Task actionTask = action(item);

        tasks.Add(actionTask);
    }

    await Task.WhenAll(tasks);
}

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

Я понимаю, что async/await работает несколько иначе в подобных случаях, и что нет явных гарантий относительно того, в каком потоке (потоках) будет выполняться асинхронный код, если нет контекста синхронизации; тем не менее, я не назначаю явно какой-либо код собственному потоку с new Thread, ThreadPool.QueueUserWorkItem или Task.Run в любой точке этого кода.

Когда я искал это в Google, я увидел документацию о разнице между идентификатором управляемого потока, а также о том, как получить идентификатор из потока. Однако на самом деле они не отвечают на вопрос, как .NET Framework вообще их придумала.

Я также хорошо знаком с окном Visual Studio Threads, которое показывает идентификатор, связанный процесс, управляемый идентификатор, категорию, имя и расположение потоков. Это также не совсем отвечает на вопрос о том, как структура назначила их для начала.


  • CLR просто начинает с 1 и считает вверх. Но вы смотрите на идентификатор потока операционной системы, фреймворк вообще не связан с ним, и вы видите их только из уведомлений отладчика. То, как он их выбирает, намеренно не документировано. То, что отладчик не показывает управляемый идентификатор в своих уведомлениях, возможно, является недостатком. 25.05.2018
  • @HansPassant Вероятно, это может быть ответом. Это очень конструктивная информация. 25.05.2018

Ответы:


1

Это не так.

Существует два разных объекта: собственные потоки, создаваемые операционной системой, и управляемые потоки, создаваемые CLR. Идентификатор потока поступает из ОС, ManagedThreadID — из среды CLR. Оба являются счетчиками, так как ОС имеет гораздо больший пул объектов потока во время запуска CLR. Идентификаторы потоков уникальны для всей ОС, потоки CLR уникальны только для каждого процесса.

Управляемый поток — это, по сути, структура данных, которая хранится в разделе TLS-памяти собственного потока, и CLR может редактировать ее, что позволяет управляемому потоку переключаться с одного собственного потока на другой и размещать несколько управляемых потоков поверх одного. собственный поток через API волокна. Вы можете использовать Thread.BeginThreadAffinity, чтобы привязать управляемый поток к тому же собственному потоку. Кроме того, для настольных приложений важно, чтобы их основной управляемый поток сопоставлялся с одним и тем же собственным потоком (ОС отправляет сообщения для перерисовки, события клавиатуры и множество других вещей в цикл сообщений определенного собственного потока, CLR подхватывает его), поэтому основной поток в этих приложениях требуется модель Single Thread Apartment, которая это обеспечивает.

25.05.2018
Новые материалы

Кластеризация: более глубокий взгляд
Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

Как написать эффективное резюме
Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

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

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

Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..