Потоки у каждого есть их собственный стек, но они совместно используют общую "кучу".
Его ясное всем, что стек для локальных переменных / переменных метода и "кучи", для экземпляра/переменных класса.
Что является преимуществом совместного использования "кучи" среди потоков.
Существует несколько количества потоков, работающих одновременно, так совместное использование памяти может привести к проблемам, таким как параллельная модификация, взаимное исключение и т.д. наверху. Какое содержание совместно используется потоками в "куче".
Почему имеет место это? Почему бы не каждый поток владеют его собственной "кучей" также? Кто-либо может обеспечить пример реального мира этого, как общая память используется потоками?
Что вы делаете, когда хотите передать данные из одного потока в другой? (Если бы вы никогда этого не делали, вы бы писали отдельные программы, а не одну многопоточную.) Есть два основных подхода:
Подход, который вы, кажется, принимаете как должное, - это разделяемая память : за исключением для данных, у которых есть веская причина быть зависящими от потока (например, стек), все данные доступны для всех потоков. В принципе, есть общая куча. Это дает вам скорость : каждый раз, когда поток изменяет некоторые данные, другие потоки могут это видеть. (Ограничение: это неверно, если потоки выполняются на разных процессорах: там программисту нужно особенно усердно работать, чтобы правильно использовать разделяемую память и эффективно.) Большинство основных императивных языков, в частности Java и C #, отдайте предпочтение этой модели.
Можно иметь одну кучу на поток плюс общую кучу. Это требует, чтобы программист решил, какие данные куда поместить, а это часто плохо сочетается с существующими языками программирования.
Двойной подход - это передача сообщений : каждый поток имеет собственное пространство данных; когда поток хочет общаться с другим потоком, ему необходимо явно отправить сообщение другому потоку, чтобы скопировать данные из кучи отправителя в кучу получателя. В этой настройке многие сообщества предпочитают вызывать процессы потоков. Это дает вам безопасность : поскольку поток не может перезаписать память другого потока по прихоти, можно избежать множества ошибок. Еще одним преимуществом является дистрибутив : вы можете запускать потоки на разных машинах, не изменяя ни одной строчки в программе. Вы можете найти библиотеки передачи сообщений для большинства языков, но интеграция, как правило, менее эффективна. Хорошими языками для понимания передачи сообщений являются Erlang и JoCaml .
На самом деле среды передачи сообщений обычно используют совместно используемую память за сценой, по крайней мере, до тех пор, пока потоки выполняются на одной машине / процессоре. Это экономит много времени и памяти, поскольку передача сообщения из одного потока в другой не требует создания копии данных. Но поскольку разделяемая память не предоставляется программисту, присущая ей сложность ограничивается реализацией языка / библиотеки.
Во многих языках / средах выполнения стек (среди прочего) используется для хранения параметров и переменных функции / метода. Если бы поток разделял стек, все стало бы очень запутанно.
void MyFunc(int a) // Stored on the stack
{
int b; // Stored on the stack
}
Когда вызов MyFunc завершен, стек выталкивается и a и b больше не находятся в стеке. Поскольку потоки не используют общие стеки, нет проблем с потоками для переменных a и b.
Из-за характера стека (выталкивание / выталкивание) он не совсем подходит для сохранения «долгосрочного» состояния или общего состояния между вызовами функций. Примерно так:
int globalValue; // stored on the heap
void Foo()
{
int b = globalValue; // Gets the current value of globalValue
globalValue = 10;
}
void Bar() // Stored on the stack
{
int b = globalValue; // Gets the current value of globalValue
globalValue = 20;
}
void main()
{
globalValue = 0;
Foo();
// globalValue is now 10
Bar();
// globalValue is now 20
}
Процессы - как правило - не разделяют пространство кучи. Существуют API, позволяющие это сделать, но по умолчанию процессы являются отдельными
. Потоки совместно используют пространство кучи.
Это «практическая идея» - два способа использования памяти - совместно используемая и не разделяемая.
Куча - это просто вся память вне стека, которая выделяется динамически. Поскольку ОС предоставляет единое адресное пространство, становится ясно, что куча по определению совместно используется всеми потоками в процессе. Что касается того, почему стеки не являются общими, это связано с тем, что поток выполнения должен иметь свой собственный стек, чтобы иметь возможность управлять своим деревом вызовов (например, оно содержит информацию о том, что делать, когда вы покидаете функцию!).
Теперь вы могли бы, конечно, написать диспетчер памяти, который выделял бы данные из разных областей в вашем адресном пространстве в зависимости от вызывающего потока, но другие потоки все равно могли бы видеть эти данные (как если бы вы каким-то образом пропустили указатель на что-то в стеке вашего потока в другой поток, чтобы этот другой поток мог его прочитать, несмотря на то, что это ужасная идея)
Потому что иначе они были бы процессами. В этом и заключается вся идея потоков - разделять память.
Проблема в том, что наличие локальных куч добавляет значительную сложность за очень небольшую стоимость.
Есть небольшое преимущество в производительности, и это хорошо обрабатывается TLAB (Thread Local Allocation Buffer), который прозрачно дает вам большую часть преимущества.
Это потому, что идея потоков - "делиться всем". Конечно, есть некоторые вещи, которые вы не можете разделить, например контекст процессора и стек, но все остальное разделяется.