Why not Green Threads?
Whilst I know questions on this have been covered already (e.g. https://stackoverflow.com/questions/5713142/green-threads-vs-non-green-threads), I don’t feel like I’ve got a satisfactory answer. The question is: why don’t JVM’s support green threads anymore? It says this on the code-style Java FAQ:
A green thread refers to a mode of operation for the Java Virtual Machine (JVM) in which all code is executed in a single operating system thread.
The downside is that using green threads means system threads on Linux are not taken advantage of and so the Java virtual machine is not scalable when additional CPUs are added.
It seems to me that the JVM could have a pool of system processes equal to the number of cores, and then run green threads on top of that. This could offer some big advantages when you have a very large number of threads which block often (mostly because current JVM’s cap the number of threads). Thoughts?
To me, the question seems: Why green threads? Why re-introduce multithreading by emulating it at JVM level via multiple processes? It’s a lot of pain and overhead for seemingly no gain other than allowing programmers being more generous with spawning threads (and I’m not convinced that’s an advantage).
Well, it’s about having a concurrent programming model which scales. Currently, in Java, if you want scalability you switch to NIO with your own thread pool. At least, that’s my understanding.
The presence of things like which supports lightweight threads also makes me think there is a need. Actually, just found quite a good discussion here
@delnan Because context switch for native threads costs. Green threads have much less overhead for context switch and interprocess syncs. In addition, the amount of green threads is practically unlimited (it can be hundreds of thousands of them without too much stress for VM process), while amount of native threads is restricted by OS and memory overhead.
It took a long time before the JVM supported native threads directly. Green threads was the intermediate solution until then.
3 Answers 3
I remember the JVM abandoning green threads and moving to native threads. This was for two simple reasons: the green threads were frankly rubbish, and there was a need to support multi-core processors with the limited developer effort available at Sun.
This was a shame — green threads provide a far better abstraction, allowing concurrency to be a useful tool not a stumbling block. But green threads are no use if several hurdles can’t be overcome:
- they must use all the cpu cores available to them
- context switching must be cheap
- I/O may block any thread engaged in it, but not any other thread and certainly not all other threads, which was the case in some early implementations.
I’ve often wondered why multi-threading is so hard in Java but it’s now becoming clearer — it was ultimately to do with the switch to native threads, which are:
- good at using all the cpu cores
- good at being truly concurrent, providing independent I/O etc
- slow at context switching (compared with the best green thread implementations)
- horribly greedy with memory, hence limiting the maximum usable number of them
- a poor abstraction for any basis for expressing the real world, which is highly concurrent of course.
Nowadays, a lot of programmer time now goes into coding up non-blocking I/O, futures etc. It’s a big shame we don’t have a better level of abstraction.
For comparison, besides Erlang the new Go language does a good job of huge concurrency. The grand-daddy of them all remains Occam, still an ongoing research project.
Обзор моделей работы с потоками
Многие люди не понимают того, как многопоточность реализована в различных языках программирования. В наши времена многоядерных процессоров такое знание будет весьма полезно.
Вот вам небольшой обзор.
Начало (С и native threads)
Первая модель, которую мы рассмотрим — это стандартные потоки ОС (threads). Их поддерживает каждая современная ОС, несмотря на разницу в API. В принципе, поток — это процесс выполнения инструкций, который работает на выделенном процессоре, выполнение которого контролирует планировщик (scheduler) ОС, и который может быть заблокирован. Потоки создаются внутри процесса и пользуются общими ресурсами. Это означает, что, например, память и декскрипторы файлов, являются общими для всех потоков процесса. Подобный подход и принято называть native threads.
Linux позволяет использовать данные потоки с помощью библиотеки pthread. BSDs тоже поддерживают pthreads. Потоки Windows работают немного иначе, но базовый принцип тот же.
Java and Green Threads
Когда появилась Java, она принесла с собой другой тип многопоточности, который называется green threads. Green threads — это, по сути, имитация потоков. Виртуальная машина Java берёт на себя заботу о переключении между разными green threads, а сама машина работает как один поток ОС. Это даёт несколько преимуществ. Потоки ОС относительно дороги в большинстве POSIX-систем. Кроме того, переключение между native threads гораздо медленнее, чем между green threads.
Это всё означает, что в некоторых ситуациях green threads гораздо выгоднее, чем native threads. Система может поддерживать гораздо большее количество green threads, чем потоков OС. Например, гораздо практичнее запускать новый green thread для нового HTTP-соединения к веб-серверу, вместо создания нового native thread.
Однако есть и недостатки. Самый большой заключается в том, что вы не можете исполнять два потока одновременно. Поскольку существует только один native thread, только он и вызывается планировщиком ОС. Даже если у вас несколько процессоров и несколько green threads, только один процессор может вызывать green thread. И всё потому, что с точки зрения планировщика заданий ОС всё это выглядит одним потоком.
Начиная с версии 1.2 Java поддерживает native threads, и с тех пор они используются по умолчанию.
Python
Python — это один из моих любимейших скриптовых языков и он был одним из первых предложивших работу с потоками. Python включает в себя модуль, позволяющий манипулировать native threads, поэтому он может пользоваться всеми благами настоящей многопоточности. Но есть и одна проблема.
Python использует глобальную блокировку интерпретатора (Global Interpreter Lock, GIL). Эта блокировка необходима для того, чтобы потоки не могли испортить глобальное состояние интерпретатора. Поэтому две инструкции Python не могут выполняться одновременно. GIL снимается примерно каждые 100 инструкций и в этот момент другой поток может перехватить блокировку и продолжить своё выполнение.
Сперва это может показаться серьёзным недостатком, однако на практике проблема не столь велика. Любой заблокированный поток как правило освобождает GIL. Расширения С также освобождают её когда не взаимодействуют с Python/C API, поэтому интенсивные вычисления можно перенести в C и избежать блокировки выполняющихся потоков Python. Единственная ситуация, когда GIL действительно представляет проблему — это ситуация когда поток Python пытается выполняться на многоядерной машине.
Stackless Python — это версия Python, которая добавляет “tasklets” (фактически green threads). По их мотивам был создан модуль greenlet, который совместим со де-факто стандартом: cPython.
Ruby
Модель потоков Ruby постоянно меняется. Изначально Ruby поддерживал лишь собственную версию green threads. Это хорошо работает во многих сценариях, но не даёт пользоваться возможностями многопроцессорности.
JRuby перевёл потоки Ruby в стандартные потоки Java, которые, как мы выяснили выше, являются native threads. И это создало проблемы. Потокам Ruby нет необходимости взаимно синхронизоваться. Каждому потоку гарантируется, что никакой другой поток не получит доступа к используемому общему ресурсу. Подобное поведение было сломано в JRuby, так как native threads переключаются принудительно (preemptive) и поэтому любой поток может обратиться к общему ресурсу в произвольное время.
Из-за подобной несостыковки и желания получить native threads разработчиками C Ruby было решено, что Ruby будет переходить на них в версии 2.0. В состав Ruby 1.9 был включён новый интерпретатор, который добавил поддержку fibers, которое, насколько я знаю, являются более эффективной версией green threads.
Короче, модель потоков Ruby — это плохо документированная каша.
Perl
Perl предлагает интересную модель, которую Mozilla позаимстовала для SpiderMonkey, если я не ошибаюсь. Вместо использования глобальной блокировки интерпретатора как в Python, Perl сделал глобальное состояние локальным и фактически запускает новый интерпретатор для каждого нового потока. Это позволяет использовать настоящие native threads. Не обошлось и без пары загвоздок.
Во-первых, вы должны явно указывать переменные доступными для других потоков. Вот что происходит, когда всё становится локальным для потока. Приходится синхронизировать значения для межпоточного взаимодействия.
Во-вторых, создание нового потока стало очень дорогой операцией. Интерпретатор — большая штука и многократное копирование его съедает много ресурсов.
Erlang, JavaScript, C# and so on
Существует множество других моделей, которым время от времени находят применение. Например Erlang, использует архитектуру «ничего-общего» (shared nothing), которая стимулирует использование лёгких пользовательских процессов вместо потоков. Подобная архитектура просто великолепна для параллельного программирования, так как она устраняет всю головную боль насчёт синхронизации, а процессы настолько легки, что вы можете создать любое их количество.
JavaScript обычно не воспринимается как язык, который поддерживает работу с потоками, но она необходима и там. Модель работы с потоками в JavaScript очень похожа на ту, что используется в Perl.
C# использует native threads.
От себя: досаду на некоторую поверхностность статьи (которую я и сам осознаю) адресуйте автору. Я всего-навсего перевёл в меру своих скромных возможностей. 😉 Буду рад уточнениям и дополнениям в комментариях.
От себя 2: по мотивам комментариев таки подправил пару фраз. Прости, автор! 🙂
Что такое «зелёные потоки» и есть ли они в Java?
Зелёные (легковесные) потоки(green threads) — потоки эмулируемые виртуальной машиной или средой исполнения. Создание зелёного потока не подразумевает под собой создание реального потока ОС.
Виртуальная машина Java берёт на себя заботу о переключении между разными green threads, а сама машина работает как один поток ОС. Это даёт несколько преимуществ. Потоки ОС относительно дороги в большинстве POSIX-систем. Кроме того, переключение между native threads гораздо медленнее, чем между green threads.
Это всё означает, что в некоторых ситуациях green threads гораздо выгоднее, чем native threads. Система может поддерживать гораздо большее количество green threads, чем потоков OС. Например, гораздо практичнее запускать новый green thread для нового HTTP-соединения к веб-серверу, вместо создания нового native thread.
Однако есть и недостатки. Самый большой заключается в том, что вы не можете исполнять два потока одновременно. Поскольку существует только один native thread, только он и вызывается планировщиком ОС. Даже если у вас несколько процессоров и несколько green threads, только один процессор может вызывать green thread. И всё потому, что с точки зрения планировщика заданий ОС всё это выглядит одним потоком.
Начиная с версии 1.2 Java поддерживает native threads, и с тех пор они используются по умолчанию.