Потоки Java и количество ядер

У меня просто был быстрый вопрос о том, как работают процессоры и потоки. Согласно моему нынешнему пониманию, ядро ​​может выполнять только 1 процесс за один раз. Но мы можем создать пул потоков (скажем, 30) с большим количеством, чем количество ядер, которыми мы обладаем (скажем, 4), и запустить их одновременно. Как это возможно, если у нас всего 4 ядра? Я также могу запустить свою 30-поточную программу на своем локальном компьютере, а также продолжать выполнять другие действия на моем компьютере, такие как просмотр фильмов или работа в Интернете.

Я где-то читал, что планирование потоков происходит, и это создает иллюзию того, что эти 30 потоков работают одновременно с 4 ядрами. Верно ли это, и если да, то может ли кто-нибудь объяснить, как это работает, а также порекомендовать почитать это?

Заранее благодарю за помощь.

4 ответа

Решение

Процессы против потоков

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

Параллелизм против параллелизма

Когда два потока работают параллельно, они оба работают одновременно. Например, если у нас есть два потока, A и B, их параллельное выполнение будет выглядеть так:

CPU 1: A ------------------------->

CPU 2: B ------------------------->

Когда два потока работают одновременно, их выполнение перекрывается. Перекрытие может происходить одним из двух способов: либо потоки выполняются в одно и то же время (т.е. параллельно, как указано выше), либо их исполнения чередуются на процессоре, например так:

CPU 1: A -----------> B ----------> A -----------> B -------- ->

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

планирование

Но мы можем создать пул потоков (скажем, 30) с большим количеством, чем количество ядер, которыми мы обладаем (скажем, 4), и запустить их одновременно. Как это возможно, если у нас всего 4 ядра?

В этом случае они могут работать одновременно, потому что планировщик ЦП дает каждому из этих 30 потоков некоторую долю времени ЦП. Некоторые потоки будут работать параллельно (если у вас 4 ядра, то одновременно будут работать 4 потока одновременно), но все 30 потоков будут работать одновременно. Тогда вы можете играть в игры или просматривать веб-страницы, потому что эти новые потоки добавляются в пул / очередь потоков, а также получают долю процессорного времени.

Логические и физические ядра

Согласно моему нынешнему пониманию, ядро ​​может выполнять только 1 процесс за раз

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

Это удивительное умение называется одновременной многопоточностью (или, как правило, Hyper-Threading, хотя это частное имя для конкретного экземпляра такой технологии). Таким образом, у нас есть физические ядра, которые являются фактическими аппаратными ядрами ЦП, и логические ядра, то есть число ядер, которое, по словам операционной системы, доступно для использования программным обеспечением. Логические ядра - это, по сути, абстракция. В типичных современных процессорах In tel каждое физическое ядро ​​действует как два логических ядра.

Может кто-нибудь объяснить, как это работает, а также порекомендовать хорошее чтение по этому вопросу?

Я бы порекомендовал концепции операционной системы, если вы действительно хотите понять, как процессы, потоки и планирование работают вместе.

Java не выполняет планирование потоков, оно оставляет это в операционной системе для планирования потоков.

Для задач, требующих большого объема вычислений, рекомендуется иметь размер пула потоков, равный количеству доступных ядер. Но для задач ввода-вывода у нас должно быть большее количество потоков. Есть много других вариантов, если оба типа задач доступны и требуют времени процессора.

ядро может выполнять только 1 процесс за раз

Да, но они могут работать в многозадачном режиме и создавать иллюзию, что они обрабатывают более одного процесса одновременно

Как это возможно, если у нас всего 4 ядра? Я также могу запустить свою программу 30 потоков на моем локальном компьютере, а также продолжать выполнять другие действия на моем компьютере

Это возможно благодаря многозадачности (что является параллелизмом). Допустим, вы запустили 30 потоков, а ОС также запускает 50 потоков, все 80 потоков будут использовать 4 ядра ЦП, получая срез времени ЦП один за другим (по одному потоку на ядро ​​за раз). Это означает, что в среднем каждое ядро ​​будет работать одновременно с 80/4=20 потоками. И вы почувствуете, что все потоки / процессы работают одновременно.

может кто-нибудь объяснить, как это работает

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

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

Примечание: вы также можете исследовать актеров, что является еще одной абстракцией над потоками. Такие языки, как Erlang, Scala и т. Д., Используют актеров для выполнения задач. Одна нить может иметь сотни актеров; Каждый актер может выполнять разные задачи (аналогично потокам в Java).

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

Короче говоря, вы понимаете ядро ​​правильно. Ядро может выполнять 1 поток (иначе процесс) одновременно.

Однако ваша программа на самом деле не запускает 30 потоков одновременно. Из этих 30 потоков только 4 работают одновременно, а остальные 26 ожидают. Процессор будет планировать потоки и давать каждому потоку часть времени для запуска на ядре. Таким образом, процессор заставит все потоки работать по очереди.

Распространенное заблуждение:

Наличие большего количества потоков заставит мою программу работать быстрее.

ЛОЖЬ: наличие большего количества потоков не всегда заставит вашу программу работать быстрее. Это просто означает, что ЦП должен выполнять больше переключений, и на самом деле, слишком большое количество потоков сделает вашу программу более медленной из-за издержек, вызванных отключением всех различных процессов.

Я хотел бы добавить к превосходному ответу Gardenhead.

Насколько мне известно, даже при одновременной многопоточности (реализацией которой является технология Intel Hyperthreading) потоки, работающие на определенном ядре, должны принадлежать одному и тому же процессу. Причина в том, что каждый процесс имеет собственное виртуальное адресное пространство. Когда эти потоки хотят получить доступ к памяти, они делают это с помощью виртуального адреса. Однако если потоки принадлежат разным процессам, они используют разные виртуальные адреса для обращения к одному и тому же физическому адресу (именно это означает наличие разного виртуального адресного пространства). Следовательно, это не может работать.

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

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

Обычно ядро ​​переключается на инструкции из другого потока, когда происходит доступ к памяти или зависимости данных приводят к остановке операции. Следовательно, второй поток может уменьшить количество пузырей в конвейере процессора. Это, конечно, означает, что мы не можем ожидать двукратного ускорения — но мы можем получить большую пропускную способность за небольшие затраты на добавление набора архитектурных регистров и добавление некоторой логики, чтобы решить, какой указатель инструкции и т. д. нам нужно выбрать. Intel говорит об ускорении примерно на 20% в зависимости от приложения.

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