Java - вопрос про кусок кода с потоками
Не однозначно. Но в 99.9% случаев под виндой на сановской jre будет одинаковым.
А пояснить причину неоднозначности можешь?
В документации написано, что у потоков никаких приоритетов для захвата lock'а объекта нет, и что notify поднимает произвольный поток.
![](/images/graemlins/smile.gif)
Тебе надо дождаться, чтобы второй поток первым захватил lock, и чтобы после этого lock захватил проснувшийся по notify главный поток.
In Thread 1
In Thread 2
In SomeClass
---------------
In Thread 1
In SomeClass
In Thread 2
---------------
In Thread 1
In Thread 2
---------------
In Thread 2
In Thread 1
In SomeClass
---------------
In Thread 2
In SomeClass
In Thread 1
---------------
In Thread 2
In Thread 1
Кстати, возможны банальные зависания в случаях, когда synchronized-блок второй нити начал выполняться позже прохождения нотифаев.
Нужно не просто дождаться, а ждать и проверять условие в цикле. В приведенном коде очень много неоднозначностей, особенно учитывая то, что wait может прерываться сам по себе с InterruptedException.Я говорил о времени выполнения программы, а не о том, как делать wait. Просто описал один из возможных путей выполнения. Сам по себе wait не прерывается с исключением, он сам по себе может только выйти.
Кстати, возможны банальные зависания в случаях, когда synchronized-блок второй нити начал выполняться позже прохождения нотифаев.
![](/images/graemlins/confused.gif)
Я говорил о времени выполнения программы, а не о том, как делать wait. Просто описал один из возможных путей выполнения. Сам по себе wait не прерывается с исключением, он сам по себе может только выйти.Неправда это.
wait
...
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
synchronized (obj) {
while (<condition does not hold>)
obj.wait;
... // Perform action appropriate to condition
}
wait(long)Я где-то видел одно из объяснений этого spurious wakeup. Вроде как java-машина может таким образом пытаться предотвратить deadlock. Так что в любой программе (даже если не используется Thread.interrupt могут летать InerruptedException. Также и Thread.sleep(long) может сам по себе бросать InterruptedException.
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
(For more information on this topic, see Section 3.2.3 in Doug Lea's "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, 2000 or Item 50 in Joshua Bloch's "Effective Java Programming Language Guide" (Addison-Wesley, 2001).
Кстати, возможны банальные зависания в случаях, когда synchronized-блок второй нити начал выполняться позже прохождения нотифаев.Здесь тоже все просто: Запускается основной поток, делает lockObj.wait; запускается первая нить, входит в synchronized-блок, успешно спит с монитором, просыпается, делает нотифай и успешно завершается. Просыпается основной поток, успешно пишет свою строку, делает нотифай, завершается. Наконец-то получает монитор вторая нить, делает нотифай, слип, затем ожидает на объекте. notify делать никто не будет уже (обе остальные нити завершились, текущая нить по умолчанию будет non-daemon, т.е. завершения работы не произойдет. Ну и остается надеяться только на тот самый spurios wakeup, по которому произойдет таки InterruptedException и вторая нить тоже завершится.
Так что в любой программе (даже если не используется Thread.interrupt могут летать InerruptedException.Перечитай кусок документации, который ты запостил. (Там даже пример кода есть, неплохо было бы на него поглядеть.) И после этого внимательно подумай, что такое этот самый spurious wake up и почему он не выкидывает исключения.
synchronized(obj) {
...
while(!condition) {
try {
obj.wait;
} catch (InterruptedException e){}
}
...
}
Собственно по этому для меня нет большой разницы, проснулся он нормально или с InterruptedException.
Собственно по этому для меня нет большой разницы, проснулся он нормально или с InterruptedException.Ну это неправильно. Исключение выкидывается в том случае, когда, например, необходимо остановить рабочий поток.
Ну это неправильно.Ну почему же? Это один из нескольких вариантов. Я предпочитаю потоки останавливать, выставляя флаг завершения, который при необходимости опрашивается, для остановки делается notify монитору и все. А использование InterruptedException приведет либо к необходимости вручную выставлять флаг в различных местах, либо проверять isInterrupted (или interrupted) (но в этом случае нужно либо наследоваться от Thread, либо постоянно делать Thread.currentThread. Использование всеобъемлющих блоков с InterruptedException тоже не выход, т.к. в некоторых ситуациях есть действия, которые после ожидания в любом случае выполняются. Ну и еще удобно запускать в новых нитях рабочие методы конструкцией вида
new Thread(new Runnable {
someMethod;
}).start;
При этом метод опрашивает периодически флаг необходимости завершения. Изменение флага позволяет прекратить выполнение всех методов. В 1.5 это скорее всего нужно бы через Executors сделать, и там уже через InterruptedException обрабатывать остановку.
Да, как альтернатива для таких средств завершения есть Thread.interrupt и Thread.join но это не значит, что их нужно использовать везде. А необходимость остановки рабочего потока обычно определяется контрактами на разрабатываемый компонент. Если в требованиях системы есть необходимость останавливать по interrupt - будет соответствующий код. Нет такого требования - остановка будет производиться другими средствами.
Ппрерыванию работы программы по ^C необработка InterruptedException не мешает, так что обрабатывать или не обрабатывать InterruptedException - вопрос контрактов на компоненты ПО, а не требования платформы (нить может просто выполнять вычисления без ожиданий и не проверять статус прерывания, тогда Thread.interrupt с ней все равно ничего сделать не сможет)
Ну почему же? Это один из нескольких вариантов. Я предпочитаю потоки останавливать, выставляя флаг завершения, который при необходимости опрашивается, для остановки делается notify монитору и все. А использование InterruptedException приведет либо к необходимости вручную выставлять флаг в различных местах, либо проверять isInterrupted (или interrupted) (но в этом случае нужно либо наследоваться от Thread, либо постоянно делать Thread.currentThread.Такой механизм плохо работает со спящими или ожидающими ввода-вывода потоками. Конечно, он лучше подходит для математических потоков. В нашем случае всё-таки были спящие потоки.
т.к. в некоторых ситуациях есть действия, которые после ожидания в любом случае выполняются
Чтобы эти действия выполнились в любом случае, тебе всё равно придётся использовать finally. Что прекрасно ложится на механизм с прерыванием потока.
Да, как альтернатива для таких средств завершения есть Thread.interrupt и Thread.join но это не значит, что их нужно использовать везде.Везде не надо. Но я бы сказал наоборот, что это не альтернатива, а нормальный способ. Конечно, я бы тоже не стал использовать его в математических потоках. Но там обычно избегают любой лишней синхронизации и проверок. Так что всё-таки единственный правильный аргумент против - это скорость выполнения.
так что обрабатывать или не обрабатывать InterruptedException - вопрос контрактов на компоненты ПОПо-моему - это вопрос того, что ты хочешь сделать с исключением, а не вопрос контрактов на ПО. Если ты ничего с ним сделать не хочешь, тогда не надо ловить.
public class Test1 {
static Object lockObj = new Object;
static volatile int flag = 0;
public static void main(String[] args) throws Exception {
synchronized (lockObj) {
new Thread(new Runnable {
public void run {
synchronized (lockObj) {
flag++;
lockObj.notify;
System.out.println("In Thread 1");
}
}
}).start;
new Thread(new Runnable {
public void run {
synchronized (lockObj) {
lockObj.notify;
while(flag < 2) {
lockObj.wait;
}
lockObj.notify;
System.out.println("In Thread 2");
}
}
}).start;
while(flag < 1) {
lockObj.wait;
}
System.out.println("In SomeClass");
flag++;
lockObj.notify;
}
}
}
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:Вот отстой, руками писать цикл каждый раз. Чем это тогда лучше C?
Вот отстой, руками писать цикл каждый раз. Чем это тогда лучше C?Обычно используются уже готовые решения, так что цикл писать не надо. Не ясно сравнение с Си.
Я тут поправил пример. Вставил кое-где while, как рекомендует sun в своей доке. Теперь-то вывод программы будет однозначным?Нет. Какую задачу ты решаешь?
Вопрос "что выведет данная программа" был задан мне на собеседовании. Я затруднился дать ответ. Сейчас я понимаю, почему исходный пример неоднозначно определяет вывод. Я пробую добиться однозначности.
В общем, это задача в себе, не имеющая прикладного смысла.
Почему не однозначный?Я уже писал выше, что никаких приоритетов у notify и synchronized нет. У тебя дедлок в случае:
main wait
2 notify main
2 wait
main wait
1 notify 2
2 wait
---------------А как такое может произойти?
In Thread 1
In Thread 2
---------------
А как такое может произойти?2 wait
1 notify
2 wake
*
ну дальше 2 делает notify и будит основной
Почему эти решения не в стандартной библиотеке?
> Не ясно сравнение с Си.
Ну в POSIX тоже нужно окружать кучу функций циклами и проверять какие-нибудь EINTR и EAGAIN, особенно если это работа с сокетами, или то же ожидание процессов. В winapi наверняка то же самое, так как интерфейс сокетов скопирован.
В Java wait'ы приходится так писать по другим соображениям, не по тем же причинам, что в Си. И вообще ожидание на объекте никак не соотносится с библиотеками Posix и не имеет цели опережать или быть лучше.
А, ты точно про случай такого вывода? Тяжело сказать, надо много времени тратить либо на детальную прорисовку выполнения, либо на изучение этого кода. Да и какая разница, если написана такая чушь?
В Java wait'ы приходится так писать по другим соображениям, не по тем же причинам, что в Си.По каким же другим, если ясно написано, что возможны "самопроизвольные" пробуждения?
По каким же другим, если ясно написано, что возможны "самопроизвольные" пробуждения?Ну почитай, почему так происходит. Думаю, гугл минут за пять найдёт ответ.
Первым монитор берет 1 тред
------
- После него монитор у второго, затем у основного
In Thread 1
In SomeClass
In Thread 2
- После него монитор у основного, затем у второго
In Thread 1
In SomeClass
*
Первым монитор берет 2 тред
------
- Потом монитор у первого,
- ... потом монитор у второго, в конце — у основного
In Thread 1
In Thread 2
In SomeClass
- ... потом монитор у основного, в конце — у второго
In Thread 1
In SomeClass
In Thread 2
- Потом монитор у основного
- ... затем монитор у первого, в конце — у второго
In SomeClass
In Thread 1
In Thread 2
- ... затем монитор у второго, в конце — у первого
In SomeClass
In Thread 2
In Thread 1
Какая разница, почему, если цикл всё такой же надо писать?
В Java wait'ы приходится так писать по другим соображениямКстати, вполне может быть, что по тем же, что в Си.
![](/images/graemlins/smile.gif)
Какая разница, почему, если цикл всё такой же надо писать?Ну надо писать цикл, и что? Руки отвалятся? Ноги?
ну у меня получились вот такие случаиНу а я ваще не получал никаких случаев.
![](/images/graemlins/smirk.gif)
![](/images/graemlins/smirk.gif)
Ну на практике у меня тоже только два. А это — в теории =)
Руки отвалятся?Я бы предпочёл программирование отдельно, а физические упражнение отдельно.
Такого действительно не бывает. Я по ошибке считал, что lockObj может еще InterruptedException кинуть, в таком случае сообщение бы не выводилось.
У тебя дедлок в случае:Почему это дедлок?
main wait
2 notify main
2 wait
main wait
1 notify 2
2 wait
main уже разбужен и ждет только монитора
2 — то же самое
когда закончится первый тред кто-то из них получит монитор и все нормально завершится
main уже разбужен и ждет только монитораmain не разбужен. notify будет только одну нить. Для того, чтобы разбудить все нити нужно использовать notifyAll.
main уже разбужен и ждет только монитораА проверка условия?
main: synchronize, check (false wait
2: synchronize, notify main, check (false wait
main: synchronize, wake, check (false wait
1: synchronize, notify 2, exit
2: synchronize, wake, check (false wait
Я бы предпочёл программирование отдельно, а физические упражнение отдельно.Боюсь, что твой пример физических упражнений немного... Убогий что ли? Или просто не в кассу.
PS Для конструктива придумай задачу с синхронизацией, которую на Java будет решить тяжелее/дольше, чем на Си.
Я только это видел:
The thread then waits until it can re-obtain ownership of the monitor and resumes execution.
Ну так в примере майка было 2 нотифая. Один будит main, другой - второй тред
А что за проверка условия?while(flag < 1)
Ой! Сорри, я на первый пример смотрел
Для конструктива придумай задачу с синхронизацией, которую на Java будет решить тяжелее/дольше, чем на Си.Я не утверждал, что на С легче, а говорил, что так же.
Хотя:
1) синхронизация внутри ядра ОС - ява не при делах, пример нечестный,
2) на С можно препроцессор изнасиловать, чтобы он тот же цикл while, внутри которого case, генерировал; а вот на хорошем языке препроцессор/кодогенератор не понадобился бы
3) а как там в яве с неблокирующимся ожиданием нескольких процессов сразу? сделали уже, или нужно по треду на каждый рожать?
1) Если ОС писать на Java, то задача решается, причём более просто, чем на Си. Другое дело, что Java не предназначена для такой задачи, а поэтому говорить о том, что где и как она проще реализуется нельзя.
2) Не понял.
3) А не путаешь ли ты многопроцессное (пережиток старых *nix) и многопоточное программирование? Потоки вообще-то более выгодная вещь, так что ты ставь задачу полностью, чтобы не было недопонимания или высосанного из пальца удобства. (Конечно, в Java нет ожидания по handle нескольких процессов сразу.)
3) В виндах (и в шарпе) есть мазёвая штука под названием worker threads pool. Это такая штуковина, специально оптимизированная под большое количество нитей, находящихся в ожидании каких-то эвентов. Например multimedia timers в винде вызывают обработчик именно в нити из этого пула.
Тред пулы есть и в Джаве. Только оптимизированы они вовсе не под большое количество потоков, ожидающих эвентов. Главный смысл - в повторном использовании потоков, что позволяет экономить время на запуск и инициализацию потока.
А не путаешь ли ты многопроцессное (пережиток старых *nix) и многопоточное программирование?Не путаю, ждать нужно примерно одинаково и то, и то.
Полностью - нужно запускать функции параллельно, и получать уведомления о завершении. Как это сделать на Java, не запуская по отдельному треду для мониторинга? Заставлять функции посылать сообщения перед завершением не предлагать - допустим, их пишут другие разработчики, и нет полной уверенности, что их тред не помрёт без посылки такого сообщения.
По поводу пережитка - ты не прав в корне, разделение адресные пространства даёт свои выгоды.
Не путаю, ждать нужно примерно одинаково и то, и то.Ждать примерно одинаково? В этом ты не прав. Например, у потоков более лёгкий механизм синхронизации. На вычислительных задачах будет очень большая разница. Опять же, тред пулы реализованы на уровне ядра, что тоже имеет очень большие преимущества. Да и дебаг многопроцессных программ - это гемор, в отличие от дебага многопоточных программ.
Полностью - нужно запускать функции параллельно, и получать уведомления о завершении. Как это сделать на Java, не запуская по отдельному треду для мониторинга? Заставлять функции посылать сообщения перед завершением не предлагать - допустим, их пишут другие разработчики, и нет полной уверенности, что их тред не помрёт без посылки такого сообщения.
По поводу пережитка - ты не прав в корне, разделение адресные пространства даёт свои выгоды.
Разделённае адресные пространства дают свои выгоды в случае с Си, и выгоды эти в основном для избежания ошибок в программах. Ничего, кроме этого я не могу придумать.
Почему твоя задача лучше решается на Си мне тоже не понятно. Особенно с оговоркой, что процессы могут не вернуться. Если же нужно запускать _функции_ параллельно, то в данном случае доктор прописал треды и тред пулы.
Как это делать с процессами, я знаю - ловить сигналы.
Да и дебаг многопроцессных программ - это гемор, в отличие от дебага многопоточных программ.Совсем наоборот. Если знаешь, что данные никто, кроме самого процесса, не может поменять, отладка упрощается.
Например, у потоков более лёгкий механизм синхронизации.Это - проблемы WIndows, просто механизмы IPC там слабы, и переключение процессов медленное.
В Linux всё нормально, в том числе механизмы синхронизации доступны очень эффективные. Да, ещё ядро Linux переключает процессы примерно за то же время, что Windows - свои треды.
Другие выгоды от отдельных адресных пространств:
* использование кеша данных на SMP - при едином адресном пространстве нужно хитро размещать данные по линиям кеша, чтобы они не прыгали между процессорами; знает ли об этом Java VM?
* однопоточный GC гораздо проще многопоточного, и работает быстрее - не нужны блокировки, опять же линии кеша не прыгают, когда GC просматривает объекты и меняет флажки;
* можно писать не thread-safe код, без лишних блокировок и копирований - а в Java в стандартной библиотеке во многих местах стоят лишние блокировки на тот случай, если из разных тредов полезут к объекту;
* безопасность: за изоляцией следит ядро, даже при использовании Java лишний уровень проверок не помешает, например, на shared hosting.
При этом: если у процессов действительно есть общие данные, над которыми нужно работать совместно - никто не мешает использовать разделяемую память и эффективную синхронизацию с её помощью.
Задача, предположим, не вычислительная. То есть каждый процесс/тред работает долго, но когда он завершится, нужно узнать об этом сразу. Опционально - прибить, если не завершится за нужное время.
Как это делать с процессами, я знаю - ловить сигналы.
Предположим, есть такая задача. Хотя на практике мне тяжело представить, зачем она может понадобиться. Если не прибивать, то в Java проще запустить поток с бесконечным ожиданием. (Не ясно зачем вообще ожидать, если не прибиваем.) Если прибивать, то лучше в одном потоке организовать что-то типа пула процессов и проверять их по таймеру. Ни в одном из случаев не вижу, как это проще решается на Си.
Совсем наоборот. Если знаешь, что данные никто, кроме самого процесса, не может поменять, отладка упрощается.
Если ты дебажишь две разные программы, то это всё-таки две разные прогаммы, а не одна многопроцессная. (Только не понятно, с чего вдруг держать два работающих дебаггера проще, чем один.) А если ты дебажишь программу, в которой кусок shared memory наислуют разные процессы (о других способах гонять данные я даже не буду писать, думаю, по понятным причинам); то отладка никак не упрощается, а, как я уже отметил выше, усложняется.
Это - проблемы WIndows, просто механизмы IPC там слабы, и переключение процессов медленное.
В Linux всё нормально, в том числе механизмы синхронизации доступны очень эффективные. Да, ещё ядро Linux переключает процессы примерно за то же время, что Windows - свои треды.
(Что-то попахивает немалоизвестным сайтом линуксоидов. Кто тебе сказал такую чушь? Я много раз читал о том, почему виндовый apache рулил линуксовый. Да и знаю людей, которые в своё время сравнивали производительность тред пулов винды и *nix'ов. Если я буду продолжать, то топик выльется в холивар.) Ещё раз отмечу, что у потоков более лёгкий механизм синхронизации. Независимо от выбранной ОС.
Другие выгоды от отдельных адресных пространств:
* использование кеша данных на SMP - при едином адресном пространстве нужно хитро размещать данные по линиям кеша, чтобы они не прыгали между процессорами; знает ли об этом Java VM?
* однопоточный GC гораздо проще многопоточного, и работает быстрее - не нужны блокировки, опять же линии кеша не прыгают, когда GC просматривает объекты и меняет флажки;
* можно писать не thread-safe код, без лишних блокировок и копирований - а в Java в стандартной библиотеке во многих местах стоят лишние блокировки на тот случай, если из разных тредов полезут к объекту;
* безопасность: за изоляцией следит ядро, даже при использовании Java лишний уровень проверок не помешает, например, на shared hosting.
* Разве Linux SMP ядро не умеет запускать два потока на разных процессорах? Или делает это не оптимально? В винде, например, можно (в теории) даже контролировать, как будут запускаться потоки на процессорах. А Java VM в свою очередь активно использует native код для работы с потоками.
* GC в Java претерпел множество изменений и сейчас очень хорошо работает в многопоточных программах, даже на многопроцессорных машинах. В Си GC вообще нет, а поэтому аргумент совершенно не понятен.
* В стандартной библиотеке Java я ни разу не видел лишних блокировок, и в ней можно писать непотокобезопасный код.
* Не понял...
Если прибивать, то лучше в одном потоке организовать что-то типа пула процессов и проверять их по таймеру.Проверять по таймеру - значит, о завершении узнаешь не сразу.
Кроме того, гонять цикл проверки вместо ожидания - неэффективно.
А если ты дебажишь программу, в которой кусок shared memory наислуют разные процессы(о других способах гонять данные я даже не буду писать, думаю, по понятным причинам); то отладка никак не упрощается, а, как я уже отметил выше, усложняется.Дело в том, что обычно довольно мало данных действительно разделяются потоками. Как правило, каждый поток в основном делает своё дело, и только иногда синхронизируется. Так что нужно только небольшие участки кода тщательно проверить на предмет корректной работы с разделяемыми данными. Да, если проблемы таки вылезут, то отлаживать сложнее (хотя, боюсь, проблемы с неправильной синхронизацией одинаково сложно искать - вон ваши споры с 'ом - вы ж не в отладчике ловили баги, а теоретически рассуждали, потому как не воспроизведётся такое в отладчике). Но зато гораздо больший объём кода отлаживать проще, так как заведомо известно, что другие потоки на его выполнение не влияют.
Почему не рассматриваешь другие способы? Экстремальная производительность далеко не всегда нужна, а в остальных случаях рулят сокеты, пайпы, очереди сообщений.
Кто тебе сказал такую чушь? Я много раз читал о том, почему виндовый apache рулил линуксовый. Да и знаю людей, которые в своё время сравнивали производительность тред пулов винды и *nix'ов.Не смог найти ни одного современного бенчмарка
![](/images/graemlins/frown.gif)
Самому, что ли, на досуге проверить? Правда, нужно чтобы виндовый код кто-нибудь написал.
С апачем проблема в другом, он от переключения контекста не сильно зависит (мало их там и от времени создания потока тоже (редко оно происходит, там же пул). Например, про знаменитый бенчмарк в 2000г Apache vs. IIS помню, ту проблему почти сразу и пофиксили.
Ещё раз отмечу, что у потоков более лёгкий механизм синхронизации.Ещё раз отмечу, что futex - самый быстрый механизм в Linux - доступен в обоих случаях, и работает одинаково.
* Разве Linux SMP ядро не умеет запускать два потока на разных процессорах? Или делает это не оптимально? В винде, например, можно (в теории) даже контролировать, как будут запускаться потоки на процессорах. А Java VM в свою очередь активно использует native код для работы с потоками.Эх, ну не знаю как объяснить, если ты не в курсе, как происходит согласование кешей на SMP. Если кому-то ещё непонятно, может попробую подробнее рассказать.
* GC в Java претерпел множество изменений и сейчас очень хорошо работает в многопоточных программах, даже на многопроцессорных машинах. В Си GC вообще нет, а поэтому аргумент совершенно не понятен.Очень хорошо - по сравнению со старыми версиями Java? Невелико достижение - старые версии тормозили нипадецки - я сам девелопером подрабатывал тогда, как раз юзали версию 1.2.
Си тут не причём (хотя GC для него бывают потому как сравниваем уже не языки, а модели многопоточного программирования.
В стандартной библиотеке Java я ни разу не видел лишних блокировок, и в ней можно писать непотокобезопасный код.Припоминаю что-то про StringBuffer.add или типа того, который thread-safe. Слышал, что потом сделали небезопасную версию, только потому, что это такой распространённый случай.
Безопасность: например, на хостинге, потоки веб-сервера обрабатывают соединения. Параллельно работают приложения разных пользователей - им совершенно нечего разделять в памяти - это же разные клиенты провайдера, если им дать общую память - будут гадить друг другу (даже если JVM настроить на запрещение доступа, лишний эшелон защиты не помешает - в JVM тоже бывают баги).
В вебе обычно взаимодействие идёт через базу данных, в этом случае совершенно незачем давать общую память потокам веб-сервера, так что никакого бенефита нет от общего адресного пространства, а недостатки все сохраняются.
Дело в том, что обычно довольно мало данных действительно разделяются потокамиСкорее всего ты имеешь ввиду вычислительные задачи, которые к реальным программам имеют отдаленное отношение.
Наличие тредов позволяет осуществить эффективное кэширование данных без лишних обращений к базе.
через процессы все сильно усложняется.
Веб-сервера, почтовые сервера ...
Даже поганый браузер - ну если он в двух табах разные странички показывает - ну зачем им общее адресное пространство?
Сервер баз данных - исключение, пожалуй, но и то - тот же Оракл то на процессах с сегментами общей памяти сделает, то на тредах - им пох, похоже - имхо так и должно быть.
наверное, да, потому что у них как раз нет понятия "взаимодействие".
> Веб-сервера
нет, потому что нормальный веб-сервер обеспечивает взаимодействие между большим кол-вом подключений.
Обращение к базе - это фигня. Оценим - пусть 100 запросов к скриптам секунду (на деле гораздо меньше и каждый скрипт обращается к базе 100 раз (на деле поменьше). Это 10000 переключений в секунду, пустяки для современного проца.
Проблема в том, как это кеширование сделать, чтоб ещё гораздо эффективнее было, чем mysql это делает, а хранить кеш даже в файлах (хоть на ram-диске, хоть на обычной ФС) можно - оверхед на системный вызов ещё меньше, чем на обращение к базе.
сеть ты куда дел?
базы с ценными данными на web-сервера обычно не ставят.
на деле эти взаимодействия проходят через толстую библиотеку (типа EJB, или там простой SQL-сервер и сделаны обычно так, чтобы разные подключения на разных узлах кластера обрабатывались - безо всякой общей памяти
но есть те самые 20%, которые все портят, и которыми хочеться обмениваться как можно быстрее.
чтобы разные подключения на разных узлах кластера обрабатывались - безо всякой общей памятивозьмем кластер, и те же самые логины.
для того, чтобы можно было свободно обслуживать этого пользователя (в любом треде, в любом процессе, на любом компе) - информация об изменении пользовательской инфы должна раскидываться всем и быстро.
при наличии тредов - такое раскидывание происходит внутри без обращении к БД.
наличие кластера этот выигрышь не обесценивает, потому что только для тех случаев, когда пользователь перекинут на другой узел кластера - происходит промах по локальному кэшу, и приходится лезть в базу.
так между узлами кластера - всё равно не получится
а если и вдруг если и есть такое редкое веб-приложение (у тебя есть примеры? то обмен этот будет опять же через специальный, разработанный для этого дела интерфейс, а не абы как, если разработчик грамотный - то есть будет явно выделенный код для этого, которому ничего не стоит и сегмент разделяемой памяти завести
единственный случай, когда треды что-то упрощают, это если при создании объекта данных не известно ещё, может ли он понадобиться в других потоках, и таких объектов - дофига, так что выгоднее всю память сделать общей, а не выделять отдельную для таких объектов
но я не знаю таких странных задач - обычно данные чётко делятся на общие и приватные
для того, чтобы можно было свободно обслуживать этого пользователя (в любом треде, в любом процессе, на любом компе) - информация об изменении пользовательской инфы должна раскидываться всем и быстро.странную задачу придумал
при наличии тредов - такое раскидывание происходит внутри без обращении к БД.
наличие кластера этот выигрышь не обесценивает, потому что только для тех случаев, когда пользователь перекинут на другой узел кластера - происходит промах по локальному кэшу, и приходится лезть в базу.
я думаю, на практике ты никогда такого не делал - иначе более ясно изложил бы
а большинство практических случаев - другие
shared memory не выход - так как при этом придется переписывать весь уже ранее написанный код.
т.к. все операции выделения памяти придется менять на новые, которые знают про shared memory.
весь код был написан до того, как понадобилась многопоточность?
не верю
> т.к. все операции выделения памяти придется менять на новые
на C++ достаточно будет operator new/delete для соотв. классов изменить
Java, наверное, будет сосать, тут уж ничего не поделаешь
Самое интересное, что кластера обычно строят те - у кого как раз реального обмена данных между пользователями нет: поисковики, новостные сайты и т.д.
> не верю
стандартные либы ты куда дел?
или стандартные ты не используешь, или у тебя стандартные либы память не выделяют?
кто-то гарантирует, что он уже не переопределен?
кто-то гарантирует, что кода мало и он в исходниках?
это стандартное решение стандартной задачи.
паттерн "write-through caching"
или стандартные ты не используешь, или у тебя стандартные либы память не выделяют?совсем стандартные - обычно таки не выделяют, или можно подменить функцию выделения, или выделяют только для приватных данных
почти всегда
для исключительных нехай юзают треды, я ж не запрещаю
Самое интересное, что кластера обычно строят те - у кого как раз реального обмена данных между пользователями нет:Куча больших корпоративных систем тоже используют кластера и балансировщики нагрузки. Какая ж иначе масштабируемость, и кто ж заплатит $$$$$$$ за хрень, не умеющую на кластере работать? А middleware для таких систем основана на message passing (у MS так, и у IBM и Oracle, которые любят Java никакого синхронного доступа к нелокальным объектам.
хочешь сказать, что управление каким-нибудь аэропортом тоже на кластере делается?
теперь приведи реальный пример, когда общий кеш для всех потоков на одном узле спасает, а кеш внутри каждого потока - слишком неэффективно, да и разделяемую память подключить неудобно, и сокеты тоже медленно/неудобно
такие бывают, может быть, но редко - твой пример с информацией о пользователе взят не из практики явно
кстати, в glibc (самая что ни на есть стандартная библиотека) есть специальное средство как раз для такого случая - nscd называется, работает общим кешом для разных процессов - то есть практика против тебя работает
сильно надеюсь, что и не на яве
хотя в наше время не исключаю ни того, ни другого
![](/images/graemlins/frown.gif)
пока не понимаю - как ты реальный пример отличаешь от нереального.
ps
с моей практической точки зрения - в одном случае - нужно платить доп. деньги за шаг влево/вправо - поэтому она плохая, в другой не нужно - значит она хорошая.
Тред пулы есть и в Джаве. Только оптимизированы они вовсе не под большое количество потоков, ожидающих эвентов. Главный смысл - в повторном использовании потоков, что позволяет экономить время на запуск и инициализацию потока.Читай внимательно, вдумчиво:
Thread PoolingВот это - зашито внутрь ядра винды и дофига где используется по дефолту (например, в Multimedia Timers).
There are many applications that create threads that spend a great deal of time in the sleeping state waiting for an event to occur. Other threads may enter a sleeping state only to be awakened periodically to poll for a change or update status information. Thread pooling enables you to use threads more efficiently by providing your application with a pool of worker threads that are managed by the system. At least one thread monitors the status of all wait operations queued to the thread pool. When a wait operation has completed, a worker thread from the thread pool executes the corresponding callback function.
А вот если ты задумаешься над своей фразой про "повторное использование и экономию на инициализации", то поймёшь, что она на самом деле бессмысленная =) Создание треда - это когда операционная система выделяет где-то у себя внутри кусочек памяти, прописывает туда параметры треда, оставляя немножко места под контекст, и запоминает в другом месте ссылку на этот кусочек памяти. Если операционная система будет "выделять" эти кусочки памяти из отдельной кучи (то есть "повторно использовать", в твоей терминологии это будет а) внутреннее дело системы, б) недостойно такого громкого собственного названия.
Что-то ты как-то слишком высокоуровнево думаешь, по-моему. В "компьютере" на самом деле нет "объектов", так что следует чётко понимать, что "повторное использование объектов" является не какой-то самостоятельной технологией, а лишь из одним из видов оптимизации работы с памятью.
совсем не вся правда.
далее этот тред надо впихнуть в общую очередь тредов, может быть пересчитать приоритеты у других процессов/тредов и т.д.
при удалении треда надо разобраться с мусором - незавершенные локи, например.
тот же, кто гарантирует, что эта библиотека реентерабельна, наверное?
объект - это что-то имеющее свое собственное состояние, а не "кусок памяти".
и как раз основное время уходит на инициализацию этого состояния, а не на выделение памяти.
пока не понимаю - как ты реальный пример отличаешь от нереальногоесли я вижу, что ты пишешь о том, в чём не разбираешься - то знаю, что пример ты взял не из практики
многопользовательских кластеров, где такие проблемы возникают, ты явно даже не видел, и со знающими людьми эти вопросы не обсуждал
хрен с ним - это твое право считать, что ты знаешь о практике больше, чем другие.
или знаешь того, кто делал, и в курсе, почему там нужно было делать именно такой кэш?
в нашей лабе как раз такой софт пишут
без тредов, что неудивительно
делал на распределенной системе.
мб тогда поподробнее опишешь, какое ускорение дало такое кеширование (по сравнению с кешом в приватной памяти потока, например и насколько много больших либ без исходников помешали сделать кеш в отдельной области памяти?
как-минимум выигрыш по памяти в кол-во раз от кол-ва потоков
> насколько много больших либ без исходников помешали сделать кеш в отдельной области памяти
.net framework
с моей практической точки зрения - в одном случае - нужно платить доп. деньги за шаг влево/вправо - поэтому она плохая, в другой не нужно - значит она хорошая.немного не так
если модель взаимодействия уже выбрана - то от неё сложнее отойти, это в обе стороны работает:
если к большому проекту захотелось треды добавить, то нужно кучу кода проверять на предмет реентерабельности, окружать критические участки локами
если пишешь на Java или .Net - за тебя модель уже выбрали Sun и MS соотв.
а при разработке под фрюниксы есть выбор реальный
куча хорошего софта написана с многими процессами, и ещё куча - с тредами
я всего-то хотел сказать, что это вовсе не "устаревшая" модель, а и для новых приложений популярная, и есть на то причины, а ты сразу начал зачем-то придумывать специальные примеры, где она не подходит
объект - это что-то имеющее свое собственное состояние, а не "кусок памяти".Перечисли мне, пожалуйста, вещи, характеризующие "собственное состояние" потока, и скажи, какие из них можно не инициализировать заново в случае реюза убитого потока из тред-пула. И сколько процессорных циклов на этом удастся сэкономить - пять, десять? =)
и как раз основное время уходит на инициализацию этого состояния, а не на выделение памяти.
Понимаешь, когда говорится "создание объекта", в воображении сразу возникает что-то трудоёмкое. На деле чистое создание и инициализация треда без запуска - это
ZeroMemory(threads[threadCount].context, THREAD_CONTEXT_SIZE);
threads[threadCount].affinity = affinity
threads[threadCount].priority = priority
// ещё несколько таких присваиваний
threads[threadCount].context.IP = threadProcAddress;
// и ещё несколько присваиваний
threadCount++;
Не правда ли? Конечно, это тоже занимает какое-то время, но не такое уж большое. Проще надо быть и не использовать высокоуровневых buzzwords там, где речь идёт о каких-то базовых вещах.
Закрытие локов при убийстве треда - это, конечно, довольно трудоёмкая задача. Но её надо делать для всех тредов, и пуловых тоже.
столько много этих данных о пользователе, что реально ощутимая экономия?
там что, видео с его участием обрабатывается?
> .net framework
понятно, что если у тебя уже были треды по другим соображениям, то проще именно так сделать
совсем другое дело, когда изначально их нет, а нужно всего лишь придумать механизмы взаимодействия между потоками
> возникает что-то трудоёмкое.
А как оно в Windows для обычных тредов? Сколько уходит на создание и удаление?
верхушка айсберга:
security
exception-ы
культура
com-овское состояние
тредонезависимое состояние стандартных библиотек (например, C)
очередь сообщений
Это, собственно, не очень важно - вряд ли "типичное приложение" будет порождать тысячу тредов в секунду, каждый из которых будет работать меньше миллисекунды и удаляться.
Напомню, что я упомянул тред-пулы, поскольку они являются простым, мощным и эффективным решением проблемы ожидания многих эвентов, реализованным на уровне ядра. Вот и всё.
Это, собственно, не очень важно - вряд ли "типичное приложение" будет порождать тысячу тредов в секунду, каждый из которых будет работать меньше миллисекунды и удаляться.Веб-сервера и прочие хрени, обрабатывающие короткие внешние запросы, было бы соблазнительно так делать, если бы потоки порождались достаточно быстро.
Напомню, что я упомянул тред-пулы, поскольку они являются простым, мощным и эффективным решением проблемы ожидания многих эвентов, реализованным на уровне ядра.Ты предлагаешь создавать на кеждый источник по треду в пуле, я правильно понял?
1к на пользователя: в основном роли, права
пользователей порядка 50 тыс.
exception-ы - эээ чо?
культура - один инт?
com-овское состояние - не знаю, не ффтыкал.
тредонезависимое состояние C-ишное библиотеки - а ось тут при чём?
очередь сообщений - инициализируется при первом вызове GetMessage/PeekMessage и как правило нафиг не нужна, так что мимо.
Ну да. Смысл в том, что следит за эвентами всегда один тред, а остальные треды вообще тредами не являются, по большому счёту. Насколько я знаю.
ох
и в каждом треде нужно кешировать все?
сервис, который будет запущен на каждом узле, и будет этим заниматься, не сделать? дополнительная выгода от этого - можно будет, скажем, обновлять собственно приложение без потери кеша; если кеш - действительно такой критичный ресурс, то это важно
а насколько важна согласованность кешей - то есть, если на одном узле данные изменились, как быстро это должны заметить остальные?
The ImpersonateLoggedOnUser function lets the calling thread impersonate the security context of a logged-on user. The user is represented by a token handle.> exception-ы - эээ чо?
навешивание обработчиков на разные варианты исключений
> культура - один инт?
начальное значение он из воздуха берет?
как это? им что, стек не нужен?
http://msdn.microsoft.com/library/default.asp?url=/library/e...
Насколько я понял - да, не нужен, пока они находятся в состоянии ожидания.
Например -
Почитай лучше Насколько я понял - да, не нужен, пока они находятся в состоянии ожидания.
Например -
To use thread pooling, the work items and all the functions they call must be thread-pool safe. A safe function does not assume that the thread executing it is a dedicated or persistent thread. In general, you should avoid using thread local storage <..>То есть если у тебя есть worker thread, который периодически просыпается по таймеру и что-то делает, никто тебе на самом деле не гарантирует, что это каждый раз один и тот же тред =)
непонятно - на основе чего ты делаешь этот вывод.
The following are best practices when using a thread pool:
The threads of the thread pool are owned by the system and not the application. Do not terminate a thread from the thread pool by calling TerminateThread on the thread or by calling ExitThread from a callback function.
Clean up all resources created in the callback function before returning from the function. These include TLS, security contexts, thread priority, and COM registration. Callback functions must also restore the thread state before returning.
Keep wait handles and their associated objects alive until the thread pool has signaled that it is finished with the handle.
Mark all threads that are waiting on lengthy operations (such as I/O flushes or resource cleanup) so that the thread pool can allocate new threads instead of waiting for this one.
Before unloading a DLL that uses the thread pool, cancel all work items, I/O, wait operations, and timers, and wait for executing callbacks to complete.
Avoid deadlocks by eliminating dependencies between work items and between callbacks, by ensuring a callback is not waiting for itself to complete, and by preserving the thread priority.
Do not queue too many items too quickly in a process with other components using the default thread pool. There is one default thread pool per process, including Svchost.exe. By default there is a maximum of 500 worker threads. To create more worker threads, the number of worker threads in the ready/running state must be less than the number of processors.
Avoid the COM single-threaded apartment model, as it is incompatible with the thread pool. STA creates thread state which can affect the next work item for the thread. STA is generally long-lived and has thread affinity, which is the opposite of the thread pool.
Create a new thread pool to control thread priority and isolation, create custom characteristics, and possibly improve responsiveness. However, additional thread pools require more system resources (threads, kernel memory). Too many pools increases the potential for CPU contention
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
Я тут вижу указатель на секьюрити атрибуты (да, наверное его обработка/проверка занимает какое-то время, но он может быть и нуллом, кстати размер стека, стартовый адрес, произвольный параметр, флаги и возвращаемый параметр. Всё остальное либо создаётся ручками потом, либо забирается из процесса - и что-то аргументы вида "начальное значение он из воздуха берет?" меня забавляют. Нет, не из воздуха. Подозреваю, что присваивание этого начального значения занимает аж целых две ассемблерных инструкции. Я не прав?
И вообще, что ты хочешь доказать, сформулируй чётко, плз.
Да, я согласен, что тред-пул даёт выигрыш ещё и за счёт отсутствия инициализации тредов, окей =) Особенно с учётом того, что на самом деле там может быть два реальных треда на 500 "виртуальных".
зачем их заранее навешивать?
наверное, уже после генерации исключения идут по стеку и смотрят, кого вызывать
на самом деле, создавать треды относительно недолго
в линуксе, на типичной тачке 2002г, в новой библиотеке NPTL 10^5 тредов создались и удалились за 2с - это полновесные треды без послаблений
из этого списка - ожидаемо следует - что пульный тред - это обычный тред, который просто большую часть времени находится в состоянии спячкиНет. Из этого списка ожидаемо следует, что пульный тред _можно_ воспринимать так в большинстве случаев, но следует соблюдать некоторые простые правила типа не использовать TLS - довольно странно для "обычного треда", не правда ли?
Далее, "состояние спячки" тут это не вызов внутри коллбэка Wait(1000 а возвращение из коллбэка.
Всё это сделано для того, чтобы ты мог создать 500 как-бы-тредов, которые будут "просыпаться" раз в секунду с интервалом в две миллисекунды и тут же засыпать, а система тебе обеспечила такое поведение с использованием всего двух настоящих тредов.
60 тыс. тактов - это не есть - недолго.
const int threadCount = 100;
const int iterations = 100;
for (int cnt = 0; cnt < iterations; cnt++)
{
HANDLE threads[threadCount];
for (int i = 0; i < threadCount; i++)
{
threads[i] = CreateThread(NULL, 128, ThreadProc, NULL, 0, NULL);
}
WaitForMultipleObjects(threadCount, threads, TRUE, INFINITE);
// Close all thread handles upon completion.
for(int i = 0; i < threadCount; i++)
{
CloseHandle(threads[i]);
}
}
сама ThreadProc сразу возвращает 0.
На моей машине (атлон64 3000+) оно занимает где-то 1.4 секунды (если поставить процессу реалтайм приорити (SetPriorityClass. Итого - 0.14 миллисекунд на создание/ожидание/уничтожение одного треда (~300,000 тактов). Пожалуй, дофига, ты прав. Хотя мб я что-то не так сделал.
Как вменяемо переписать этот пример на тред-пулах я что-то не соображу.
относительно недолго
и не 60 тыс, а где-то, наверное 30--40
если с пулами намного быстрее, то согласен, долго
const int threadCount = 100;
const int iterations = 100;
int main(void)
{
int cnt;
for (cnt=0; cnt<iterations; cnt++)
{
pid_t pids[threadCount];
int i;
for (i=0; i<threadCount; i++)
{
pids[i] = fork;
if (pids[i] == 0) { /* child */ exit(0); }
}
i=0;
while (i<threadCount)
{
int status = 0;
pid_t r = wait(&status);
if (r < 0) { /* error */
if (errno == EINTR) { continue; }
perror("error");
exit(1);
}
if (WIFEXITED(status { i++; }
}
}
return 0;
}
Настоящий fork в полный рост, дочерний процесс трогает память, чтоб жизнь мёдом не казалась.
1.05 — 1.20 с на моей тачке, AMD Sempron 3000+
Проверять по таймеру - значит, о завершении узнаешь не сразу.
Кроме того, гонять цикл проверки вместо ожидания - неэффективно.
Если процессы в твоей задаче делают какую-то короткую работу и выходят, то задача опять же решается тред пулами. Если они делают что-то реально долгое, такое, что окупается многопроцессность, то не важно, узнаешь ты о завершении сейчас или через одну секунду. Опять же, по сравнению с накладками на использование процессов, проверка раз в секунду не внесёт никакой значительной неэффективности.
хотя, боюсь, проблемы с неправильной синхронизацией одинаково сложно искать
Я искал и те, и другие на практике. В одном окне дебаггера очень приятно видеть два потока, которые хотят поднять "чужую вилку". Можно так же чётко отследить состояния всех объектов в программе. А в случае многопроцессных программ бывает и такое, что один процесс вообще скомпилирован в релиз.
Почему не рассматриваешь другие способы? Экстремальная производительность далеко не всегда нужна, а в остальных случаях рулят сокеты, пайпы, очереди сообщений.
Если не нужна производительность, тогда, конечно, shared memory не важна.
Не смог найти ни одного современного бенчмарка Помню, читал что-то.Бенчмарки искать бесполезно. Во-первых, почти наверняка найдутся только старые. Во-вторых, они кем-нибудь куплены. Я знаю, что до появления поддержки потоков в ядре linux, апач под винду рулил апач под линукс. Потом я перестал интересоваться этим вопросом. Можно покопать http://www.spec.org/ Впрочем, ещё не сложно отгуглить что-нибудь типа
Самому, что ли, на досуге проверить? Правда, нужно чтобы виндовый код кто-нибудь написал.
С апачем проблема в другом, он от переключения контекста не сильно зависит (мало их там и от времени создания потока тоже (редко оно происходит, там же пул). Например, про знаменитый бенчмарк в 2000г Apache vs. IIS помню, ту проблему почти сразу и пофиксили.
A Web server's performance is tied closely to its threading model. IPlanet uses a multiprocess, multithreaded model; Zeus and Roxen sport a multithreaded model; the others are still based solely on a preforking model, creating a new process to handle each request as opposed to creating a new thread. Process creation is resource-intensive while creating a new thread is cheap; those Web servers that use a multithreaded model are sure to outperform those that do not. Because Stronghold and IBM HTTP Server are both based on Apache 1.3.x, they still implement a preforking model. Apache 2.0 offers a multiprocess, multithreaded option, so we would expect performance to increase significantly in these products when they migrate to an Apache 2.0 base.
Ещё раз отмечу, что futex - самый быстрый механизм в Linux - доступен в обоих случаях, и работает одинаково.
Я считаю большим минусом, если самый быстрый механизм синхронизации в ОС работает с потоками так же медленно, как и с процессами. Ровно как и то, что переключение между путём выполнения и контекстом выполнения занимает одинаковое время.
Эх, ну не знаю как объяснить, если ты не в курсе, как происходит согласование кешей на SMP. Если кому-то ещё непонятно, может попробую подробнее рассказать.Да не важно, как оно происходит, Java VM тут не при чём. Она работает с потоками на уровне ОС.
Безопасность: например, на хостинге, потоки веб-сервера обрабатывают соединения. Параллельно работают приложения разных пользователей - им совершенно нечего разделять в памяти - это же разные клиенты провайдера, если им дать общую память - будут гадить друг другу (даже если JVM настроить на запрещение доступа, лишний эшелон защиты не помешает - в JVM тоже бывают баги).Но ведь есть механизмы для обеспечения безопасности на уровне потоков. Так что опять, если ОС умеет работать правильно, то в Java будет всё хорошо. Хотя в целом я понял твой довод и согласен с ним. Думаю, сюда же можно добавить второй, с которым я бы согласился: утечки памяти и прочих ресурсов.
число итераций пришлось увеличить до 1000, иначе слишком быстро работало
![](/images/graemlins/tongue.gif)
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
const int threadCount = 100;
const int iterations = 1000;
void *thread_func(void *a)
{
return NULL;
}
int main(void)
{
int cnt;
for (cnt=0; cnt<iterations; cnt++)
{
pthread_t ids[threadCount];
int i;
for (i=0; i<threadCount; i++)
{
int r = pthread_create(ids+i, NULL, &thread_func, NULL);
if (r < 0) {
perror("pthread_create");
exit(1);
}
}
for (i=0; i<threadCount; i++)
{
void *status = NULL;
int r = pthread_join(ids[i], &status);
if (r < 0) { /* error */
perror("error");
exit(1);
}
}
}
return 0;
}
На 1000 итераций - 3с.
Если не нужна производительность, тогда, конечно, shared memory не важна.Очень часто производительность нужна, но узкое место вовсе не в обмене между потоками.
Если процессы в твоей задаче делают какую-то короткую работу и выходят, то задача опять же решается тред пулами. Если они делают что-то реально долгое, такое, что окупается многопроцессность, то не важно, узнаешь ты о завершении сейчас или через одну секунду.Бывает, что некоторые работают долго, а некоторые быстро, и заранее не узнаешь, сколько будет работать. Например, получение или отправка письма по SMTP. Кроме того, в этом случае сторонние плагины, написанные на самых разных языках, могут работать, так что безопасностью JVM не отмажешься.
Я считаю большим минусом, если самый быстрый механизм синхронизации в ОС работает с потоками так же медленно, как и с процессами.В данном случае - одинаково быстро - вот это уже плюс.
Ровно как и то, что переключение между путём выполнения и контекстом выполнения занимает одинаковое время.Время не одинаковое. Треды переключаются ещё быстрее, хотя бы потому, что трансляцию страниц подменять не надо. Смотри бенчмарк.
Apache 1.3 ценится вовсе не за экстремальную производительность (для этого есть более другие сервера а за корректность, надёжность и безопасность. Модель prefork это всё обеспечивает, при приемлемой производительности (скрипты на php, perl всё равно работают гораздо дольше, чем процессы переключаются)
Да не важно, как оно происходит, Java VM тут не при чём. Она работает с потоками на уровне ОС.Зато JVM отвественна за распределение памяти внутри приложения, следовательно, за эффективное использование процессорного кеша.
А вот если ты задумаешься над своей фразой про "повторное использование и экономию на инициализации", то поймёшь, что она на самом деле бессмысленная =)Ты опять лезешь в дискуссию, либо не изучив тему, либо не поняв и не опробовав на практике. Погугли и найдёшь примерно такие статьи:
http://www.c-sharpcorner.com/Code/2002/April/MtP4MtVsMt.asp
http://66.102.9.104/search?q=cache:ylQh08cR9SIJ:java.sun.com...
http://en.wikipedia.org/wiki/Thread_pool_pattern
Всё остальное, о чём ты пишешь, представляет собой следствие.
Очень часто производительность нужна, но узкое место вовсе не в обмене между потоками.
Я писал про производительность при передаче данных.
Бывает, что некоторые работают долго, а некоторые быстро, и заранее не узнаешь, сколько будет работать. Например, получение или отправка письма по SMTP. Кроме того, в этом случае сторонние плагины, написанные на самых разных языках, могут работать, так что безопасностью JVM не отмажешься.Я не вижу, как в поставленной задаче теряется производительность при применении цикла с проверкой процессов раз в секунду.
--------------------------------------------------------------------------------
Я считаю большим минусом, если самый быстрый механизм синхронизации в ОС работает с потоками так же медленно, как и с процессами.
--------------------------------------------------------------------------------
В данном случае - одинаково быстро - вот это уже плюс.
Вот именно, что не плюс, а минус. Потому что точно можно создать объект синхронизации, который будет с потоками работать быстрее, чем с процессами. В винде такой есть. И я не верю, что в *nix'ах, при их стремлении быть лучше винды хотя бы в этих вопросах, нет таких механизмов синхронизации.
Время не одинаковое. Треды переключаются ещё быстрее, хотя бы потому, что трансляцию страниц подменять не надо. Смотри бенчмарк.
Ты сам себе противоречишь. То у тебя процессы переключаются так же, как потоки. То потоки начинают переключаться ещё быстрее. Определись с чем-то одним и ответь, что будет производительнее: потоки или процессы?
Зато JVM отвественна за распределение памяти внутри приложения, следовательно, за эффективное использование процессорного кеша.Мне всегда казалось, что ОС сама отвечает за оптимизацию кэширования кусков памяти. Поэтому я не понимаю, почему вдруг Java машина должна за этим следить? А как, например, за этим могла бы уследить программа на Си?
Я не вижу, как в поставленной задаче теряется производительность при применении цикла с проверкой процессов раз в секунду.Например, нужно сразу знать, что письмо ушло, чтобы следующее из очереди на этот же маршрут достать (у продвинутых серверов отдельные хитрые очереди и лимиты на каждый маршрут). А рядом, скажем, спам-фильтр, который иногда 10мс на письмо тратит, а иногда 10с. Раз в 10мс проверять завершение - неэффективно. Раз в секунду - будешь ждать лишнюю секунду, если всё быстро проверится - почта будет сильно дольше уходить.
Потому что точно можно создать объект синхронизации, который будет с потоками работать быстрее, чем с процессами. В винде такой есть.Что есть в винде? За счёт чего нельзя процессы так же быстро синхронизировать? Давай попробуем сравнить на практике производительность, так же как создания процессов/тредов выше.
Ты сам себе противоречишь. То у тебя процессы переключаются так же, как потоки. То потоки начинают переключаться ещё быстрее. Определись с чем-то одним и ответь, что будет производительнее: потоки или процессы?Обычные процессы в Linux, по моим сведениям, создаются и переключаются примерно так же быстро, как обычные потоки в Windows.
Обычные потоки в Linux (то есть POSIX threads) - ещё в несколько раз быстрее.
Всякие извращения (легковесные потоки, которые целиком в юзерспейсе, или там пресловутые не-совсем-настоящие-потоки из пула) - может быть, ещё быстрее.
Про создание процессов и тредов - смотри результаты выше.
Про переключение - можно попробовать добавить дополнительные переключения.
Мне всегда казалось, что ОС сама отвечает за оптимизацию кэширования кусков памяти.Нет, традиционная ОС этого сделать не может. То есть может для структур ядра, но не для данных приложения.
Например, нужно сразу знать, что письмо ушло, чтобы следующее из очереди на этот же маршрут достать (у продвинутых серверов отдельные хитрые очереди и лимиты на каждый маршрут). А рядом, скажем, спам-фильтр, который иногда 10мс на письмо тратит, а иногда 10с. Раз в 10мс проверять завершение - неэффективно. Раз в секунду - будешь ждать лишнюю секунду, если всё быстро проверится - почта будет сильно дольше уходить.
Это уже задача для потоков, и реализация с тред пулом (или mixed) порулит любую другую.
Что есть в винде? За счёт чего нельзя процессы так же быстро синхронизировать? Давай попробуем сравнить на практике производительность, так же как создания процессов/тредов выше.
Как-то вы странно сравниваете производительность процессов и потоков выше. Ставь реальную задачу, а не пустые циклы. (Так и не нашёл, где у тебя дочерний процесс трогает память.)
Про создание процессов и тредов - смотри результаты выше.
Какие результаты? Не вижу ничего, чтобы можно было считать результатом. Ставь реальную практическую задачу.
Нет, традиционная ОС этого сделать не может. То есть может для структур ядра, но не для данных приложения.
Замечательно, так в чём разница между Java и тем же Си в этом вопросе?
Это уже задача для потоков, и реализация с тред пулом (или mixed) порулит любую другую.Естественно, порулит, если на Java по-другому и не сделать.
Ставь реальную задачу, а не пустые циклы.Есть такое понятие - микро-бенчмарк. Это было оно, вопросы - к , я только портировал под POSIX. Лишнего дня для кодирования реальной задачи у меня, увы, нет пока.
Замечательно, так в чём разница между Java и тем же Си в этом вопросе?На Си можно вручную правильно сгруппировать данные, написав свой аллокатор. Кроме того, мы сравниваем уже не Java и C, а процессы и потоки. Так вот, при отдельных адресных пространствах такой проблемы просто нет.
Естественно, порулит, если на Java по-другому и не сделать.Java тут не при чём: порулит на любом языке.
Есть такое понятие - микро-бенчмарк. Это было оно, вопросы - к , я только портировал под POSIX. Лишнего дня для кодирования реальной задачи у меня, увы, нет пока.По этим "бенчмаркам" вообще ничего не ясно для проведения сравнения работы потоков на двух разных ОС.
На Си можно вручную правильно сгруппировать данные, написав свой аллокатор. Кроме того, мы сравниваем уже не Java и C, а процессы и потоки. Так вот, при отдельных адресных пространствах такой проблемы просто нет.В Java размещение объектов и работа с памятью уже грамотно реализованы. На Си ты никогда не сгруппируешь данные так, чтобы у тебя всё и везде было хорошо. (Если только не напишешь какую-нибудь маленькую программу без реального практического смысла.) При отдельных адресных пространствах возникает такая же проблема, почему нет?
По этим "бенчмаркам" вообще ничего не ясно для проведения сравнения работы потоков на двух разных ОС.Известна производительность создания+удаления потоков.
В Java размещение объектов и работа с памятью уже грамотно реализованы.Покажи хотя бы в рекламе строчку, где написано про предотвращение ненужных прыжков линий кеша.
На Си ты никогда не сгруппируешь данные так, чтобы у тебя всё и везде было хорошо.Обычно выделяют узкое место профилированием, и оптимизируют его.
При отдельных адресных пространствах возникает такая же проблема, почему нет?Когда поймёшь суть проблемы, этот вопрос отпадёт сам.
Известна производительность создания+удаления потоков.
Ты бы взял на работу программиста, который бы решал многопоточные time critical задачи таким образом? (Каждый раз создавая и удаляя поток.) Я могу переписать ваш код так, что он вычислительно сделает всё то же самое. Но будет работать быстрее.
Обычно выделяют узкое место профилированием, и оптимизируют его.
То же самое ты можешь сделать в Java.
Когда поймёшь суть проблемы, этот вопрос отпадёт сам.
Вопрос отпадёт только тогда, когда ты мне покажешь многопроцессный http сервер, который работает быстрее многопоточного. Как я уже сказал, простые программки, не несущие практического смысла, отпадают.
Ты бы взял на работу программиста, который бы решал многопоточные time critical задачи таким образом? (Каждый раз создавая и удаляя поток.)Если такое решение проще, а производительность приемлема - значит, программист сделал правильно. В ряде случаев так оно и бывает. Premature optimization ... продолжение знаешь?
То же самое ты можешь сделать в Java.Профилировать - сможешь, а свой аллокатор - уже сложнее.
А если адресные пространства разные, то и оптимизировать ничего не надо.
многопроцессный http серверпосмотри на nginx, например
только апач всё равно - самый популярный, несмотря на меньшую производительность, и я объяснил почему
Если такое решение проще, а производительность приемлема - значит, программист сделал правильно. В ряде случаев так оно и бывает. Premature optimization ... продолжение знаешь?
Тогда я не понимаю, о каком сравнении производительности идёт речь, если она не нужна? Программист всё равно сделал неправильно. Это как поставить список туда, где нужен мэп. Потом получится так, что в этом списке появляется всё больше элементов, и программа умирает.
А если адресные пространства разные, то и оптимизировать ничего не надо.
Я уже сказал, что накладки по использованию процессов превзойдут все такие оптимизации. Зачем смотреть на nginx - неудачный пример, т.к. у него нет multithreaded option. Я забыл отметить, что сравнивать надо одинаковые программы.
Это как поставить список туда, где нужен мэп.Пока у твоей фирмы 10 клиентов, их можно хранить в списке. Когда станет 10000, и список станет подтормаживать, программист реализует более сложную структуру, с учётом данных профилирования. А пока клиентов мало, мало и денег, а актуальных задач много - пусть программист займётся чем-нибудь полезным.
Я забыл отметить, что сравнивать надо одинаковые программы.Если перейти от процессов к потокам при сохранении того же уровня безопасности,надёжности и поддерживаемости, дизайн изменится, и программы перестанут быть одинаковыми.
Пока у твоей фирмы 10 клиентов, их можно хранить в списке. Когда станет 10000, и список станет подтормаживать, программист реализует более сложную структуру, с учётом данных профилирования. А пока клиентов мало, мало и денег, а актуальных задач много - пусть программист займётся чем-нибудь полезным.Это в корне неверный подход к программированию. Хотя твой пример никак не отображает того, что я сказал. Хороший программист в случае, когда нужен map, пишет именно map, а не list.
Если перейти от процессов к потокам при сохранении того же уровня безопасности,надёжности и поддерживаемости, дизайн изменится, и программы перестанут быть одинаковыми.
Все вэб серверы, имеющие бенчмарк и у которых есть опции многопоточного/многопроцессного выполнения, имеют более высокую производительность на потоках.
Это в корне неверный подход к программированию.Религиозный вопрос. Многие считают, что верный.
Все вэб серверы, имеющие бенчмарк и у которых есть опции многопоточного/многопроцессного выполнения, имеют более высокую производительность на потоках.Какие ты знаешь, кроме апача и надстроек на его основе?
Производительность растёт, а надёжность и корректность падают. И модули переписывать приходится, так как API новое пришлось делать. В mod_php, кажись, до сих пор не все глюки отловили после переписывания.
Ещё ты хотел рассказать про чудесные механизмы синхронизации потоков в Windows.
Производительность растёт, а надёжность и корректность падают.Надёжность падает как раз у тех, кто списки использует вместо потоков.
А вообще я считаю разговор законченным со следующим заключением:
- Процессы лучше потоков только в плане memory/resource leak.
- В плане безопасности вопрос не ясен, потому что я не знаю, на каком уровне реализована безопасность потоков в *nix. (А в винде она есть.)
- Java использует native код для работы с потоками, процессами и прочее. Поэтому никакого преимущества Си не имеет.
- Была упомянута чисто теоретическая возможность оптимизации многопроцессных программ под кэш. Опять же, вопрос до конца не ясен и не описан.
- Запуск/останов потоков в линуксе в некотором приближении быстрее, чем в винде.
- Переключение процессов в линуксе вроде как быстрее, чем в винде.
- В линуксе отсутствует примитив синхронизации, работающий на уровне потоков, вследствие чего имеющий более высокую производительность.
- Одинаковые программы, написанные под потоки и под процессы, имеют разительно большую производительность в варианте с потоками.
- Дебаг многопоточных программ гораздо проще/легче, чем дебаг многопроцессных.
Заметно, что кончились аргументы.
От повторения недоказанных суждений они ведь не станут верными.
![](/images/graemlins/smirk.gif)
> Религиозный вопрос. Многие считают, что верный.
Не знаю ни одного примера с противоположной стороны,
ты можешь показать хотя бы парочку, чтобы было "многие," а не "все?"
---
...Я работаю антинаучным аферистом...
Заметно, что кончились аргументы.Нет, заметно, что кто-то перешёл на личности.
От повторения недоказанных суждений они ведь не станут верными.
Что касается аргументов, они все названы. Ссылки на сайты и цитаты я привёл. Писать программу для реального практического теста ты отказался. Давил всё время на security процессов, как на огромное преимущество. Я уже сказал, что в винде потоки могут работать с разным security, а поэтому твои аргументы о безопасности применимы разве что к линуксу, если в нём нету такого механизма. Про кэш: во-первых, ничего не объяснил; во-вторых, не привёл никаких доказательств, что оно вообще возможно, а для меня философский взгляд не является аргументом.
> Поэтому никакого преимущества Си не имеет.
В конце концов, всё сводится к native code,
только при этом объёмы native code очень разные.
Я не вижу причин, почему ява хотя бы сравнима с сями.
> - Дебаг многопоточных программ гораздо проще/легче, чем дебаг многопроцессных.
"От обоснуя и слышу."
---
...Я работаю антинаучным аферистом...
Видел код только -а, но не твой.
---
"Я знаю правду! Все прежние правды --- прочь!"
Я уже сказал, что в винде потоки могут работать с разным securityЗащиты памяти-то нет, так что это только для удобства (имперсонирование, например а не для защиты.
Про кэш: во-первых, ничего не объяснил; во-вторых, не привёл никаких доказательств, что оно вообще возможноА ты бы спросил по-хорошему, или в гугле бы поискал. А то пока, похоже, только ты не знаешь, что это за проблема. Объясню попозже, раз ты не в теме совсем.
Писать программу для реального практического теста ты отказался.Напиши сам, а я портирую на Linux, с тредами и с процессами, если найду время на выходных.
Сам-то ты не назвал волшебные средства синхронизации потоков в винде, которые быстрее линуксовых.
"От обоснуя и слышу."Как показала жизненная практика, ты умеешь:
- Пиздеть.
Как показала жизненная практика ты не умеешь:
- Программировать.
- Понимать, как работают программы.
- Считать до двух.
А посему тебе следует:
- Кликнуть сюда http://www.diablo.jino-net.ru/ и любоваться.
Одинаковые программы, написанные под потоки и под процессы, имеют разительно большую производительность в варианте с потоками.Надо понимать, что в случае веб-сервера разница видна только в специально придуманных тестах. На практике же, например, многие админы считают, что второй апач - отстой, и юзают 1.3. Потому что 1% производительности менее важен, чем надёжность и безопасность, с которыми у apache 2 до сих пор не очень.
Даже на хостингах, где производительность очень важна. Навскидку - masterhost, zenon. Также 1.3 стоит на yandex.ru и mail.ru - тоже нагрузка там неслабая, наверное.
Защиты памяти-то нет, так что это только для удобства (имперсонирование, например а не для защиты.
Для защиты в том числе. Так что я всё равно не считаю твой довод таким уж значительным.
А ты бы спросил по-хорошему, или в гугле бы поискал. А то пока, похоже, только ты не знаешь, что это за проблема. Объясню попозже, раз ты не в теме совсем.
Я не в теме совсем, почему это работает с процессами и не работает с потоками. Или хотя бы почему при этом многопроцессная программа решает многопоточную. И я не думаю, что возможна такая оптимизация, разве что в очень редких случаях.
Надо понимать, что в случае веб-сервера разница видна только в специально придуманных тестах. На практике же, например, многие админы считают, что второй апач - отстой, и юзают 1.3. Потому что 1% производительности менее важен, чем надёжность и безопасность
Бенчмарки сейчас довольно комплексные, можно посмотреть конкретные интересующие данные. Опять же, производительность растёт не на 1%, а от 50% и выше.
Для защиты в том числе. Так что я всё равно не считаю твой довод таким уж значительным.Если тред А имеет доступ к (почти) всей памяти треда B, то он (почти всегда) может выполнить что угодно с теми правами, которые есть у B.
Если нужна безопасность, приложение проектируют таким образом, чтобы модули общались между собой только через чётко прописанные интерфейсы, а по-другому не могли. Это, как бы, на первом занятии по теме должны рассказывать.
Как удостовериться, что модули не могут общаться в обход интерфейсов? Если они в отдельных процессах, то за этим следит ядро. А если у них общая память, то один другому может испоганить любые данные, если захочет (или случайно, тогда это угроза надёжности, а не безопасности).
Дебаг многопоточных программ гораздо проще/легче, чем дебаг многопроцессных.Чем посылать Контру, лучше бы действительно обосновал это очень и очень спорное утверждение.
+rambler.ru
![](/images/graemlins/blush.gif)
Спереди nginx, сзади apache.
А что у зенона спереди, не знаешь? Раньше (в 2000г) вроде сквид был, обратил внимание на характерные ошибки.
Чем посылать Контру, лучше бы действительно обосновал это очень и очень спорное утверждение.Я уже обосновал его выше: ты видишь сразу все данные, у тебя запущен один дебаггер, ты одним действием можешь останавливать и запускать программу, ты можешь поставить брейкпоинт (при дебаге многопроцессных программ если один процесс после fork делает exec, то тебе придётся дебаггером аттачиться к новому процессу да и далеко не все дебаггеры отслеживают запуск новых процессов.
Я уже обосновал его выше: ты видишь сразу все данные, у тебя запущен один дебаггер, ты одним действием можешь останавливать и запускать программу, ты можешь поставить брейкпоинт (при дебаге многопроцессных программ если один процесс после fork делает exec, то тебе придётся дебаггером аттачиться к новому процессу да и далеко не все дебаггеры отслеживают запуск новых процессов.В многопроцессной системе не может быть такой ошибки, в которую вовлечены два процесса. Поэтому нет необходимости запускать два дебаггера. Зато в однопроцессорной многопоточной системе могут быть ошибки такого рода, которые очень тяжело дебажить.
В многопроцессной системе не может быть такой ошибки, в которую вовлечены два процесса. Поэтому нет необходимости запускать два дебаггера. Зато в однопроцессорной многопоточной системе могут быть ошибки такого рода, которые очень тяжело дебажить.А два процесса и два каких-нибудь мьютекса, которые лочатся в разном порядке - это не ошибка?
Да, я тебя прекрасно понял, когда ты описал эту проблему в первый раз. И как я написал выше, довод считаю более-менее убедительным, и только. Я на практике никогда не занимался этой проблемой, но думаю, что в инете есть статьи, как правильно делать безопасные многопоточные программы. В конечном итоге, можно применять смесь процессов и потоков, возлагая на потоки все функции программы, а на процесс - безопасность памяти.
В многопроцессной системе не может быть такой ошибки, в которую вовлечены два процессаа куда делись гонки?
опять же не понятно, почему не может быть одновременного обращения к каким-то общим ресурсам: файлам, портам и т.д.
код - не идентичен оригинальному коду, и понятно, что скорее всего поэтому работает быстрее.
чего он не делает?
начинает закрывать потоки, не дожидаясь полного окончания всех тредов
если windows лишнего вызова требует, я не виноват
1. Как устроен кеш? Обмен между ним и памятью идёт по строчкам. То есть, вся память поделена на строчки, некоторые из которых могут присутствовать в кеше. Размер строчки зависит от конкретной модели процессора, порядка 16..64 байт вроде бы обычно. Когда процессор читает из памяти, целая строчка попадает в кеш. Когда процессор пишет в память, целая строчка отмечается как "грязная", и в конце концов некоторое время копируется в память.
2. А если процессоров несколько? Обычно у каждого процессора свой кеш. То есть, одна и та же строчка может присутствовать сразу в нескольких кешах. Если все они "чистые", то всё просто. А вот если какой-то процессор захочет туда что-то записать, ему нужно сообщить другим, что их варианты стали неверны. Для этого он сначала получает эксклюзивный доступ к этой строчке, сообщая остальным процессорам, что они должны вычистить её из своих кешей. Потом проводит запись. Если на другом процессоре опять понадобится эта строчка, то ему нужно будет взять её либо из памяти, либо из кеша первого процессора (в зависимости от реализации) - это примерно на порядок дольше, чем из L2-кеша. Если это действительно разделяемые данные, то никуда не денешься - нужно проводить межпроцессорный обмен всё равно.
3. Какая в связи с этим возможна подстава? Вроде бы всё понятно. Если данные нужны только на одном процессоре, то он спокойно с ними работает. Если на нескольких сразу, то при каждой модификации происходит межпроцессорный обмен. Однако, может оказаться так, что в одной строчке кеша оказались семантически разные объекты данных. Пусть строчка делится между объектами A и B. Пусть есть потоки 1 и 2, выполняющиеся в данный момент на разных процессорах. Пусть поток 1 активно работает с объектом A, а поток 2 - с объектом B. Если, скажем, поток 1 изменил объект A, то объект B пропал из кеша, и поток 2 будет грузить его заново. Таким образом, строчка прыгает между процессорами, создавая паразитный трафик на шине и приводя к лишним задержкам. Запросто может оказаться, что неудачно написанный код на двух процессорах будет исполняться медленнее, чем на одном.
4. Как с этим бороться? Нужно делать так, чтобы в одной строчке не оказывались разные объекты, с которыми идёт работа в разных тредах.
Конкретнее, известны такие рецепты:
a) истинно разделяемые объекты часто имеет смысл выравнивать по границе строчки: если, это мьютексы какие-нибудь, то нельзя их ставить в одну строчку, а то возможны лишние задержки при блокировке, если данный мьютекс не занят, а занят совершенно левый, соседний; однако, есть и исключения - если это целый массив мьютексов, например, к которому идёт доступ по результатом хеш-функции, то такие коллизии маловероятны, их можно и упаковать поплотнее;
б) если объект (в основном) приватен для какого-то потока, то рядом с ним надо класть подобные же ему; совсем хорошо, если они и использоваться будут близко во времени;
в) если в разделяемой структуре есть поля, часто изменяемые, а есть (почти) постоянные, то нужно сгруппировать первые отдельно от вторых, чтобы прыгало поменьше строчек;
... наверняка я не все трюки знаю.
Соответственно, идеальный аллокатор должен иметь информацию о том, как будут использоваться создаваемые объекты, и грамотно её учитывать.
Можно, например, предположить, что если объекты создаются в каком-то треде, то там же в основном и будут использоваться. Но тут нас ждёт жестокий облом с разделяемыми объектами: их ведь тоже в каком-то треде нужно создавать, а аллокатор не в курсе. Кроме того, может быть удобно передавать объекты в "долговременное пользование" другим потокам.
Получается, не обойтись без подсказок со стороны программиста. На C/С++ для ключевых объектов можно сделать свой аллокатор, и ручками там всё расставить. На Java - облом, никак не передать подсказку.
А что с отдельными адресными пространствами? А там всё хорошо, так как приватные объекты явно отделены от разделяемых (последние сидят либо в общей памяти, либо как-то явно передаются через какой-нибудь IPC). Приватные объекты можно выделять без боязни стандартным аллокатором, без подсказок. Подсказки нужны только для разделямых объектов.
В общем адресном пространстве тоже можно так сделать, конечно (если стандартный аллокатор имеет выделяет в каждом потоке из отдельного пула, и если при этом можно свой аллокатор реализовать для разделяемых объектов). Только вот при проектировании многопроцессного приложения разработчик сразу учитывает эти вещи, а в многопоточном - может какой-нибудь рефакторинг понадобиться.
В FAQ что ли засунь, если всё правда.
64 октета --- это всего лишь 16 32-разрядных слов, то есть не так уж и много.
А если 16 --- и того меньше.
Покупайте больше памяти!
---
...Я работаю антинаучным аферистом...
Какая в связи с этим возможна подстава?
Знаешь, чтобы происходило всё, о чём ты написал, надо реально постараться. Думаю, что такая проблема возможна, но эта возможность в реальной практике будет очень маленькой. Объекты можно ещё располагать на стеке, т.к. судя по выкладкам проблема связана с частым использованием стоящих рядом маленьких объектов (или конца одного - начала другого, что менее вероятно) в разных потоках.
Только вот при проектировании многопроцессного приложения разработчик сразу учитывает эти вещи, а в многопоточном - может какой-нибудь рефакторинг понадобиться.Как из всего этого следует, что два процесса, работающих с одним куском shared memory, полностью застрахованы от этой проблемы? Как я понял, никак не следует. Да и передача разделяемых объектов через сокет или пайп по времени будет дольше, чем промахи кэша. В общем, я примерно понял, что за проблема, но считаю, что плюсов от использования потоков больше, чем теоритеческий шанс нарваться на проблемы с кэшированием.
Со стеком ещё хуже.
Если выделение памяти из кучи можно распылять по адресному пространству,
что ещё можно как-то прогнозировать (отслеживать частоту обращений по страницам или строкам
то выделение памяти из стека не оставляет даже такой возможности.
Соответственно, если ты передашь указатель в кучу,
то при некоторых условиях он будет с хорошей надёжностью
выровнен по границе строки, со стеком ты можешь не иметь такой гарантии.
Если ты выделяешь память из кучи, ты можешь постараться
выделить область из места, где меньше толкотни,
со стеком это невозможно.
---
...Я работаю антинаучным аферистом...
Думаю, что такая проблема возможна, но эта возможность в реальной практике будет очень маленькой.Зависит от приложения.
В ядре Линукса (оно сильно многопоточное, с (почти) общим адресным пространством и кучей разделяемых объектов) то и дело что-то оптимизируют по этому поводу: поля переставляют, выравнивание добавляют, per-cpu-пулы вводят.
А если мало разделяемых объектов (на практике это гораздо чаще бывает то будет невесело, если они случайно перемешаются в куче. На стеке всё не выделишь, кучу всё-таки не зря придумали.
> будет невесело, если они случайно перемешаются в куче.
Почему?
Разве трансляцию страниц отменили?
---
...Я работаю антинаучным аферистом...
ну на самом деле всего лишь в несколько раз медленнее, чем IP-пакет обработать
то есть, полновесное TCP-соединение открыть и закрыть получается дольше, чем тред удалить и создать, так что я понимаю тех, кто пишет сервера по принципу один тред на соединение (особенно любителей Windows и Solaris, в которых треды довольно быстрые, а TCP-стек - не особо).
Зависит от приложения.Ядро ОС - сильно частный случай, и я нигде в этом треде не спорил, что Си+асемблер лучше всего подходят для этого. И ещё я писал, что ОС должна уметь работать с кэшем. И помогать другим программам, например, выравнивая размещённые в куче куски памяти.
В ядре Линукса (оно сильно многопоточное, с (почти) общим адресным пространством и кучей разделяемых объектов) то и дело что-то оптимизируют по этому поводу: поля переставляют, выравнивание добавляют, per-cpu-пулы вводят.
Ядро ОС - сильно частный случайЯ не смог навскидку придумать такие же сложные многопоточные приложения.
Всё, что я знаю, или многопроцессное (а то и распределённое либо однопоточное и не масшабируется на несколько процессоров.
Оставить комментарий
livemix
Однозначно ли определен вывод программы, и, если да, то какой он будет?