Java - вопрос про кусок кода с потоками

livemix

Однозначно ли определен вывод программы, и, если да, то какой он будет?
 public class Test1 {
static Object lockObj = new Integer(0);
public static void main(String[] args) throws Exception {
synchronized (lockObj) {
new Thread(new Runnable {
public void run {
synchronized (lockObj) {
try {
Thread.sleep(5000);
} catch (Exception ex) {
}
lockObj.notify;
System.out.println("In Thread 1");
}
}
}).start;
new Thread(new Runnable {
public void run {
synchronized (lockObj) {
lockObj.notify;
try {
Thread.sleep(1000);
lockObj.wait;
} catch (Exception ex) {
}
lockObj.notify;
System.out.println("In Thread 2");
}
}
}).start;
lockObj.wait;
System.out.println("In SomeClass");
lockObj.notify;
}
}
}

kokoc88

Не однозначно. Но в 99.9% случаев под виндой на сановской jre будет одинаковым.

livemix

А пояснить причину неоднозначности можешь?

kokoc88

В документации написано, что у потоков никаких приоритетов для захвата lock'а объекта нет, и что notify поднимает произвольный поток.

livemix

ок. убедительно

kokoc88

Тебе надо дождаться, чтобы второй поток первым захватил lock, и чтобы после этого lock захватил проснувшийся по notify главный поток.

Svyatogor

Нужно не просто дождаться, а ждать и проверять условие в цикле. В приведенном коде очень много неоднозначностей, особенно учитывая то, что wait может прерываться сам по себе с InterruptedException. Дальше примеры

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-блок второй нити начал выполняться позже прохождения нотифаев.

kokoc88

Нужно не просто дождаться, а ждать и проверять условие в цикле. В приведенном коде очень много неоднозначностей, особенно учитывая то, что wait может прерываться сам по себе с InterruptedException.
Я говорил о времени выполнения программы, а не о том, как делать wait. Просто описал один из возможных путей выполнения. Сам по себе wait не прерывается с исключением, он сам по себе может только выйти.
Кстати, возможны банальные зависания в случаях, когда synchronized-блок второй нити начал выполняться позже прохождения нотифаев.

Svyatogor

Я говорил о времени выполнения программы, а не о том, как делать 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)
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).
Я где-то видел одно из объяснений этого spurious wakeup. Вроде как java-машина может таким образом пытаться предотвратить deadlock. Так что в любой программе (даже если не используется Thread.interrupt могут летать InerruptedException. Также и Thread.sleep(long) может сам по себе бросать InterruptedException.
Кстати, возможны банальные зависания в случаях, когда synchronized-блок второй нити начал выполняться позже прохождения нотифаев.
Здесь тоже все просто: Запускается основной поток, делает lockObj.wait; запускается первая нить, входит в synchronized-блок, успешно спит с монитором, просыпается, делает нотифай и успешно завершается. Просыпается основной поток, успешно пишет свою строку, делает нотифай, завершается. Наконец-то получает монитор вторая нить, делает нотифай, слип, затем ожидает на объекте. notify делать никто не будет уже (обе остальные нити завершились, текущая нить по умолчанию будет non-daemon, т.е. завершения работы не произойдет. Ну и остается надеяться только на тот самый spurios wakeup, по которому произойдет таки InterruptedException и вторая нить тоже завершится.

kokoc88

Так что в любой программе (даже если не используется Thread.interrupt могут летать InerruptedException.
Перечитай кусок документации, который ты запостил. (Там даже пример кода есть, неплохо было бы на него поглядеть.) И после этого внимательно подумай, что такое этот самый spurious wake up и почему он не выкидывает исключения.

Svyatogor

Перечитал. Да, я был не прав, сам по себе исключений он не кидает, а может просто просыпаться. Ну а кусок кода у меня в подобных случаях выглядит так:

synchronized(obj) {
...
while(!condition) {
try {
obj.wait;
} catch (InterruptedException e){}
}
...
}

Собственно по этому для меня нет большой разницы, проснулся он нормально или с InterruptedException.

kokoc88

Собственно по этому для меня нет большой разницы, проснулся он нормально или с InterruptedException.
Ну это неправильно. Исключение выкидывается в том случае, когда, например, необходимо остановить рабочий поток.

Svyatogor

Ну это неправильно.
Ну почему же? Это один из нескольких вариантов. Я предпочитаю потоки останавливать, выставляя флаг завершения, который при необходимости опрашивается, для остановки делается 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 с ней все равно ничего сделать не сможет)

kokoc88

Ну почему же? Это один из нескольких вариантов. Я предпочитаю потоки останавливать, выставляя флаг завершения, который при необходимости опрашивается, для остановки делается notify монитору и все. А использование InterruptedException приведет либо к необходимости вручную выставлять флаг в различных местах, либо проверять isInterrupted (или interrupted) (но в этом случае нужно либо наследоваться от Thread, либо постоянно делать Thread.currentThread.
Такой механизм плохо работает со спящими или ожидающими ввода-вывода потоками. Конечно, он лучше подходит для математических потоков. В нашем случае всё-таки были спящие потоки.
т.к. в некоторых ситуациях есть действия, которые после ожидания в любом случае выполняются

Чтобы эти действия выполнились в любом случае, тебе всё равно придётся использовать finally. Что прекрасно ложится на механизм с прерыванием потока.
Да, как альтернатива для таких средств завершения есть Thread.interrupt и Thread.join но это не значит, что их нужно использовать везде.
Везде не надо. Но я бы сказал наоборот, что это не альтернатива, а нормальный способ. Конечно, я бы тоже не стал использовать его в математических потоках. Но там обычно избегают любой лишней синхронизации и проверок. Так что всё-таки единственный правильный аргумент против - это скорость выполнения.
так что обрабатывать или не обрабатывать InterruptedException - вопрос контрактов на компоненты ПО
По-моему - это вопрос того, что ты хочешь сделать с исключением, а не вопрос контрактов на ПО. Если ты ничего с ним сделать не хочешь, тогда не надо ловить.

livemix

Я тут поправил пример. Вставил кое-где while, как рекомендует sun в своей доке. Теперь-то вывод программы будет однозначным?
 
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;
}
}
}

Marinavo_0507

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?

kokoc88

Вот отстой, руками писать цикл каждый раз. Чем это тогда лучше C?
Обычно используются уже готовые решения, так что цикл писать не надо. Не ясно сравнение с Си.

kokoc88

Я тут поправил пример. Вставил кое-где while, как рекомендует sun в своей доке. Теперь-то вывод программы будет однозначным?
Нет. Какую задачу ты решаешь?

livemix

Почему не однозначный?
Вопрос "что выведет данная программа" был задан мне на собеседовании. Я затруднился дать ответ. Сейчас я понимаю, почему исходный пример неоднозначно определяет вывод. Я пробую добиться однозначности.
В общем, это задача в себе, не имеющая прикладного смысла.

kokoc88

Почему не однозначный?
Я уже писал выше, что никаких приоритетов у notify и synchronized нет. У тебя дедлок в случае:
main wait
2 notify main
2 wait
main wait
1 notify 2
2 wait

otets-mihail

---------------
In Thread 1
In Thread 2
---------------
А как такое может произойти?

kokoc88

А как такое может произойти?
2 wait
1 notify
2 wake
*

otets-mihail

ну дальше 2 делает notify и будит основной

Marinavo_0507

> Обычно используются уже готовые решения, так что цикл писать не надо.
Почему эти решения не в стандартной библиотеке?
> Не ясно сравнение с Си.
Ну в POSIX тоже нужно окружать кучу функций циклами и проверять какие-нибудь EINTR и EAGAIN, особенно если это работа с сокетами, или то же ожидание процессов. В winapi наверняка то же самое, так как интерфейс сокетов скопирован.

kokoc88

Как это не в стандартной? Много многопоточных (*каламбур) решений в стандартной библиотеке java.util.concurrent
В Java wait'ы приходится так писать по другим соображениям, не по тем же причинам, что в Си. И вообще ожидание на объекте никак не соотносится с библиотеками Posix и не имеет цели опережать или быть лучше.

kokoc88

А, ты точно про случай такого вывода? Тяжело сказать, надо много времени тратить либо на детальную прорисовку выполнения, либо на изучение этого кода. Да и какая разница, если написана такая чушь?

Marinavo_0507

В Java wait'ы приходится так писать по другим соображениям, не по тем же причинам, что в Си.
По каким же другим, если ясно написано, что возможны "самопроизвольные" пробуждения?

kokoc88

По каким же другим, если ясно написано, что возможны "самопроизвольные" пробуждения?
Ну почитай, почему так происходит. Думаю, гугл минут за пять найдёт ответ.

otets-mihail

ну у меня получились вот такие случаи:
Первым монитор берет 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

Marinavo_0507

Какая разница, почему, если цикл всё такой же надо писать?

kokoc88

В Java wait'ы приходится так писать по другим соображениям
Кстати, вполне может быть, что по тем же, что в Си. Только всё равно мне не понятно сравнение "чем это лучше Си".

kokoc88

Какая разница, почему, если цикл всё такой же надо писать?
Ну надо писать цикл, и что? Руки отвалятся? Ноги?

kokoc88

ну у меня получились вот такие случаи
Ну а я ваще не получал никаких случаев. Ну разве что только два различных.

otets-mihail

Ну на практике у меня тоже только два. А это — в теории =)

Marinavo_0507

Руки отвалятся?
Я бы предпочёл программирование отдельно, а физические упражнение отдельно.

Svyatogor

Такого действительно не бывает. Я по ошибке считал, что lockObj может еще InterruptedException кинуть, в таком случае сообщение бы не выводилось.

otets-mihail

У тебя дедлок в случае:
main wait
2 notify main
2 wait
main wait
1 notify 2
2 wait
Почему это дедлок?
main уже разбужен и ждет только монитора
2 — то же самое
когда закончится первый тред кто-то из них получит монитор и все нормально завершится

Svyatogor

main уже разбужен и ждет только монитора
main не разбужен. notify будет только одну нить. Для того, чтобы разбудить все нити нужно использовать notifyAll.

kokoc88

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

kokoc88

Я бы предпочёл программирование отдельно, а физические упражнение отдельно.
Боюсь, что твой пример физических упражнений немного... Убогий что ли? Или просто не в кассу.
PS Для конструктива придумай задачу с синхронизацией, которую на Java будет решить тяжелее/дольше, чем на Си.

otets-mihail

А что за проверка условия?
Я только это видел:
The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

otets-mihail

Ну так в примере майка было 2 нотифая. Один будит main, другой - второй тред

kokoc88

А что за проверка условия?
while(flag < 1)

otets-mihail

Ой! Сорри, я на первый пример смотрел

Marinavo_0507

Для конструктива придумай задачу с синхронизацией, которую на Java будет решить тяжелее/дольше, чем на Си.
Я не утверждал, что на С легче, а говорил, что так же.
Хотя:
1) синхронизация внутри ядра ОС - ява не при делах, пример нечестный,
2) на С можно препроцессор изнасиловать, чтобы он тот же цикл while, внутри которого case, генерировал; а вот на хорошем языке препроцессор/кодогенератор не понадобился бы
3) а как там в яве с неблокирующимся ожиданием нескольких процессов сразу? сделали уже, или нужно по треду на каждый рожать?

kokoc88

Что так же в Си? Работает wait-функция? Да, так же. Не вижу, как бы это влияло на сложность кода.
1) Если ОС писать на Java, то задача решается, причём более просто, чем на Си. Другое дело, что Java не предназначена для такой задачи, а поэтому говорить о том, что где и как она проще реализуется нельзя.
2) Не понял.
3) А не путаешь ли ты многопроцессное (пережиток старых *nix) и многопоточное программирование? Потоки вообще-то более выгодная вещь, так что ты ставь задачу полностью, чтобы не было недопонимания или высосанного из пальца удобства. (Конечно, в Java нет ожидания по handle нескольких процессов сразу.)

bleyman

3) В виндах (и в шарпе) есть мазёвая штука под названием worker threads pool. Это такая штуковина, специально оптимизированная под большое количество нитей, находящихся в ожидании каких-то эвентов. Например multimedia timers в винде вызывают обработчик именно в нити из этого пула.

kokoc88

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

Marinavo_0507

А не путаешь ли ты многопроцессное (пережиток старых *nix) и многопоточное программирование?
Не путаю, ждать нужно примерно одинаково и то, и то.
Полностью - нужно запускать функции параллельно, и получать уведомления о завершении. Как это сделать на Java, не запуская по отдельному треду для мониторинга? Заставлять функции посылать сообщения перед завершением не предлагать - допустим, их пишут другие разработчики, и нет полной уверенности, что их тред не помрёт без посылки такого сообщения.
По поводу пережитка - ты не прав в корне, разделение адресные пространства даёт свои выгоды.

kokoc88

Не путаю, ждать нужно примерно одинаково и то, и то.
Полностью - нужно запускать функции параллельно, и получать уведомления о завершении. Как это сделать на Java, не запуская по отдельному треду для мониторинга? Заставлять функции посылать сообщения перед завершением не предлагать - допустим, их пишут другие разработчики, и нет полной уверенности, что их тред не помрёт без посылки такого сообщения.
По поводу пережитка - ты не прав в корне, разделение адресные пространства даёт свои выгоды.
Ждать примерно одинаково? В этом ты не прав. Например, у потоков более лёгкий механизм синхронизации. На вычислительных задачах будет очень большая разница. Опять же, тред пулы реализованы на уровне ядра, что тоже имеет очень большие преимущества. Да и дебаг многопроцессных программ - это гемор, в отличие от дебага многопоточных программ.
Разделённае адресные пространства дают свои выгоды в случае с Си, и выгоды эти в основном для избежания ошибок в программах. Ничего, кроме этого я не могу придумать.
Почему твоя задача лучше решается на Си мне тоже не понятно. Особенно с оговоркой, что процессы могут не вернуться. Если же нужно запускать _функции_ параллельно, то в данном случае доктор прописал треды и тред пулы.

Marinavo_0507

Задача, предположим, не вычислительная. То есть каждый процесс/тред работает долго, но когда он завершится, нужно узнать об этом сразу. Опционально - прибить, если не завершится за нужное время.
Как это делать с процессами, я знаю - ловить сигналы.
Да и дебаг многопроцессных программ - это гемор, в отличие от дебага многопоточных программ.
Совсем наоборот. Если знаешь, что данные никто, кроме самого процесса, не может поменять, отладка упрощается.
Например, у потоков более лёгкий механизм синхронизации.
Это - проблемы WIndows, просто механизмы IPC там слабы, и переключение процессов медленное.
В Linux всё нормально, в том числе механизмы синхронизации доступны очень эффективные. Да, ещё ядро Linux переключает процессы примерно за то же время, что Windows - свои треды.
Другие выгоды от отдельных адресных пространств:
* использование кеша данных на SMP - при едином адресном пространстве нужно хитро размещать данные по линиям кеша, чтобы они не прыгали между процессорами; знает ли об этом Java VM?
* однопоточный GC гораздо проще многопоточного, и работает быстрее - не нужны блокировки, опять же линии кеша не прыгают, когда GC просматривает объекты и меняет флажки;
* можно писать не thread-safe код, без лишних блокировок и копирований - а в Java в стандартной библиотеке во многих местах стоят лишние блокировки на тот случай, если из разных тредов полезут к объекту;
* безопасность: за изоляцией следит ядро, даже при использовании Java лишний уровень проверок не помешает, например, на shared hosting.
При этом: если у процессов действительно есть общие данные, над которыми нужно работать совместно - никто не мешает использовать разделяемую память и эффективную синхронизацию с её помощью.

kokoc88

Задача, предположим, не вычислительная. То есть каждый процесс/тред работает долго, но когда он завершится, нужно узнать об этом сразу. Опционально - прибить, если не завершится за нужное время.
Как это делать с процессами, я знаю - ловить сигналы.

Предположим, есть такая задача. Хотя на практике мне тяжело представить, зачем она может понадобиться. Если не прибивать, то в 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 я ни разу не видел лишних блокировок, и в ней можно писать непотокобезопасный код.
* Не понял...

Marinavo_0507

Если прибивать, то лучше в одном потоке организовать что-то типа пула процессов и проверять их по таймеру.
Проверять по таймеру - значит, о завершении узнаешь не сразу.
Кроме того, гонять цикл проверки вместо ожидания - неэффективно.
А если ты дебажишь программу, в которой кусок shared memory наислуют разные процессы(о других способах гонять данные я даже не буду писать, думаю, по понятным причинам); то отладка никак не упрощается, а, как я уже отметил выше, усложняется.
Дело в том, что обычно довольно мало данных действительно разделяются потоками. Как правило, каждый поток в основном делает своё дело, и только иногда синхронизируется. Так что нужно только небольшие участки кода тщательно проверить на предмет корректной работы с разделяемыми данными. Да, если проблемы таки вылезут, то отлаживать сложнее (хотя, боюсь, проблемы с неправильной синхронизацией одинаково сложно искать - вон ваши споры с 'ом - вы ж не в отладчике ловили баги, а теоретически рассуждали, потому как не воспроизведётся такое в отладчике). Но зато гораздо больший объём кода отлаживать проще, так как заведомо известно, что другие потоки на его выполнение не влияют.
Почему не рассматриваешь другие способы? Экстремальная производительность далеко не всегда нужна, а в остальных случаях рулят сокеты, пайпы, очереди сообщений.
Кто тебе сказал такую чушь? Я много раз читал о том, почему виндовый apache рулил линуксовый. Да и знаю людей, которые в своё время сравнивали производительность тред пулов винды и *nix'ов.
Не смог найти ни одного современного бенчмарка Помню, читал что-то.
Самому, что ли, на досуге проверить? Правда, нужно чтобы виндовый код кто-нибудь написал.
С апачем проблема в другом, он от переключения контекста не сильно зависит (мало их там и от времени создания потока тоже (редко оно происходит, там же пул). Например, про знаменитый бенчмарк в 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 тоже бывают баги).
В вебе обычно взаимодействие идёт через базу данных, в этом случае совершенно незачем давать общую память потокам веб-сервера, так что никакого бенефита нет от общего адресного пространства, а недостатки все сохраняются.

Dasar

Дело в том, что обычно довольно мало данных действительно разделяются потоками
Скорее всего ты имеешь ввиду вычислительные задачи, которые к реальным программам имеют отдаленное отношение.

Dasar

Возьмем тот же форум.
Наличие тредов позволяет осуществить эффективное кэширование данных без лишних обращений к базе.
через процессы все сильно усложняется.

Marinavo_0507

Как раз наоборот, реальные программы, с которыми постоянно сталкиваюсь.
Веб-сервера, почтовые сервера ...
Даже поганый браузер - ну если он в двух табах разные странички показывает - ну зачем им общее адресное пространство?
Сервер баз данных - исключение, пожалуй, но и то - тот же Оракл то на процессах с сегментами общей памяти сделает, то на тредах - им пох, похоже - имхо так и должно быть.

Dasar

> почтовые сервера, Даже поганый браузер
наверное, да, потому что у них как раз нет понятия "взаимодействие".
> Веб-сервера
нет, потому что нормальный веб-сервер обеспечивает взаимодействие между большим кол-вом подключений.

Marinavo_0507

> Наличие тредов позволяет осуществить эффективное кэширование данных без лишних обращений к базе.
Обращение к базе - это фигня. Оценим - пусть 100 запросов к скриптам секунду (на деле гораздо меньше и каждый скрипт обращается к базе 100 раз (на деле поменьше). Это 10000 переключений в секунду, пустяки для современного проца.
Проблема в том, как это кеширование сделать, чтоб ещё гораздо эффективнее было, чем mysql это делает, а хранить кеш даже в файлах (хоть на ram-диске, хоть на обычной ФС) можно - оверхед на системный вызов ещё меньше, чем на обращение к базе.

Dasar

> Обращение к базе - это фигня. Оценим - пусть 100 запросов к скриптам секунду (на деле гораздо меньше и каждый скрипт обращается к базе 100 раз (на деле поменьше). Это 10000 переключений в секунду, пустяки для современного проца.
сеть ты куда дел?
базы с ценными данными на web-сервера обычно не ставят.

Marinavo_0507

> нормальный веб-сервер обеспечивает взаимодействие между большим кол-вом подключений
на деле эти взаимодействия проходят через толстую библиотеку (типа EJB, или там простой SQL-сервер и сделаны обычно так, чтобы разные подключения на разных узлах кластера обрабатывались - безо всякой общей памяти

Dasar

80% данных - да
но есть те самые 20%, которые все портят, и которыми хочеться обмениваться как можно быстрее.

Dasar

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

Marinavo_0507

> но есть те самые 20%, которые все портят, и которыми хочеться обмениваться как можно быстрее
так между узлами кластера - всё равно не получится
а если и вдруг если и есть такое редкое веб-приложение (у тебя есть примеры? то обмен этот будет опять же через специальный, разработанный для этого дела интерфейс, а не абы как, если разработчик грамотный - то есть будет явно выделенный код для этого, которому ничего не стоит и сегмент разделяемой памяти завести
единственный случай, когда треды что-то упрощают, это если при создании объекта данных не известно ещё, может ли он понадобиться в других потоках, и таких объектов - дофига, так что выгоднее всю память сделать общей, а не выделять отдельную для таких объектов
но я не знаю таких странных задач - обычно данные чётко делятся на общие и приватные

Marinavo_0507

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

Dasar

> которому ничего не стоит и сегмент разделяемой памяти завести
shared memory не выход - так как при этом придется переписывать весь уже ранее написанный код.
т.к. все операции выделения памяти придется менять на новые, которые знают про shared memory.

Marinavo_0507

> так как при этом придется переписывать весь уже ранее написанный код
весь код был написан до того, как понадобилась многопоточность?
не верю
> т.к. все операции выделения памяти придется менять на новые
на C++ достаточно будет operator new/delete для соотв. классов изменить
Java, наверное, будет сосать, тут уж ничего не поделаешь

Dasar

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

Dasar

> весь код был написан до того, как понадобилась многопоточность?
> не верю
стандартные либы ты куда дел?
или стандартные ты не используешь, или у тебя стандартные либы память не выделяют?

Dasar

> на C++ достаточно будет operator new/delete для соотв. классов изменить
кто-то гарантирует, что он уже не переопределен?
кто-то гарантирует, что кода мало и он в исходниках?

Dasar

> странную задачу придумал
это стандартное решение стандартной задачи.
паттерн "write-through caching"

Marinavo_0507

или стандартные ты не используешь, или у тебя стандартные либы память не выделяют?
совсем стандартные - обычно таки не выделяют, или можно подменить функцию выделения, или выделяют только для приватных данных
почти всегда
для исключительных нехай юзают треды, я ж не запрещаю
Самое интересное, что кластера обычно строят те - у кого как раз реального обмена данных между пользователями нет:
Куча больших корпоративных систем тоже используют кластера и балансировщики нагрузки. Какая ж иначе масштабируемость, и кто ж заплатит $$$$$$$ за хрень, не умеющую на кластере работать? А middleware для таких систем основана на message passing (у MS так, и у IBM и Oracle, которые любят Java никакого синхронного доступа к нелокальным объектам.

Dasar

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

Marinavo_0507

> паттерн "write-through caching"
теперь приведи реальный пример, когда общий кеш для всех потоков на одном узле спасает, а кеш внутри каждого потока - слишком неэффективно, да и разделяемую память подключить неудобно, и сокеты тоже медленно/неудобно
такие бывают, может быть, но редко - твой пример с информацией о пользователе взят не из практики явно
кстати, в glibc (самая что ни на есть стандартная библиотека) есть специальное средство как раз для такого случая - nscd называется, работает общим кешом для разных процессов - то есть практика против тебя работает

Marinavo_0507

> хочешь сказать, что управление каким-нибудь аэропортом тоже на кластере делается?
сильно надеюсь, что и не на яве
хотя в наше время не исключаю ни того, ни другого

Dasar

> теперь приведи реальный пример
пока не понимаю - как ты реальный пример отличаешь от нереального.
ps
с моей практической точки зрения - в одном случае - нужно платить доп. деньги за шаг влево/вправо - поэтому она плохая, в другой не нужно - значит она хорошая.

bleyman

Тред пулы есть и в Джаве. Только оптимизированы они вовсе не под большое количество потоков, ожидающих эвентов. Главный смысл - в повторном использовании потоков, что позволяет экономить время на запуск и инициализацию потока.
Читай внимательно, вдумчиво:
Thread Pooling
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.
Вот это - зашито внутрь ядра винды и дофига где используется по дефолту (например, в Multimedia Timers).
А вот если ты задумаешься над своей фразой про "повторное использование и экономию на инициализации", то поймёшь, что она на самом деле бессмысленная =) Создание треда - это когда операционная система выделяет где-то у себя внутри кусочек памяти, прописывает туда параметры треда, оставляя немножко места под контекст, и запоминает в другом месте ссылку на этот кусочек памяти. Если операционная система будет "выделять" эти кусочки памяти из отдельной кучи (то есть "повторно использовать", в твоей терминологии это будет а) внутреннее дело системы, б) недостойно такого громкого собственного названия.
Что-то ты как-то слишком высокоуровнево думаешь, по-моему. В "компьютере" на самом деле нет "объектов", так что следует чётко понимать, что "повторное использование объектов" является не какой-то самостоятельной технологией, а лишь из одним из видов оптимизации работы с памятью.

Dasar

> Создание треда - это когда операционная система выделяет где-то у себя внутри кусочек памяти, прописывает туда параметры треда, оставляя немножко места под контекст, и запоминает в другом месте ссылку на этот кусочек памяти.
совсем не вся правда.
далее этот тред надо впихнуть в общую очередь тредов, может быть пересчитать приоритеты у других процессов/тредов и т.д.
при удалении треда надо разобраться с мусором - незавершенные локи, например.

Marinavo_0507

> кто-то гарантирует, что кода мало и он в исходниках?
тот же, кто гарантирует, что эта библиотека реентерабельна, наверное?

Dasar

> В "компьютере" на самом деле нет "объектов", так что следует чётко понимать
объект - это что-то имеющее свое собственное состояние, а не "кусок памяти".
и как раз основное время уходит на инициализацию этого состояния, а не на выделение памяти.

Marinavo_0507

пока не понимаю - как ты реальный пример отличаешь от нереального
если я вижу, что ты пишешь о том, в чём не разбираешься - то знаю, что пример ты взял не из практики
многопользовательских кластеров, где такие проблемы возникают, ты явно даже не видел, и со знающими людьми эти вопросы не обсуждал

Dasar

> если я вижу, что ты пишешь о том, в чём не разбираешься - то знаю, что пример ты взял не из практики
хрен с ним - это твое право считать, что ты знаешь о практике больше, чем другие.

Marinavo_0507

ты делал учёт пользовательских логинов на кластере?
или знаешь того, кто делал, и в курсе, почему там нужно было делать именно такой кэш?
в нашей лабе как раз такой софт пишут
без тредов, что неудивительно

Dasar

> ты делал учёт пользовательских логинов на кластере?
делал на распределенной системе.

Marinavo_0507

мб тогда поподробнее опишешь, какое ускорение дало такое кеширование (по сравнению с кешом в приватной памяти потока, например и насколько много больших либ без исходников помешали сделать кеш в отдельной области памяти?

Dasar

> какое ускорение дало такое кеширование (по сравнению с кешом в приватной памяти потока, например)
как-минимум выигрыш по памяти в кол-во раз от кол-ва потоков
> насколько много больших либ без исходников помешали сделать кеш в отдельной области памяти
.net framework

Marinavo_0507

с моей практической точки зрения - в одном случае - нужно платить доп. деньги за шаг влево/вправо - поэтому она плохая, в другой не нужно - значит она хорошая.
немного не так
если модель взаимодействия уже выбрана - то от неё сложнее отойти, это в обе стороны работает:
если к большому проекту захотелось треды добавить, то нужно кучу кода проверять на предмет реентерабельности, окружать критические участки локами
если пишешь на Java или .Net - за тебя модель уже выбрали Sun и MS соотв.
а при разработке под фрюниксы есть выбор реальный
куча хорошего софта написана с многими процессами, и ещё куча - с тредами
я всего-то хотел сказать, что это вовсе не "устаревшая" модель, а и для новых приложений популярная, и есть на то причины, а ты сразу начал зачем-то придумывать специальные примеры, где она не подходит

bleyman

объект - это что-то имеющее свое собственное состояние, а не "кусок памяти".
и как раз основное время уходит на инициализацию этого состояния, а не на выделение памяти.
Перечисли мне, пожалуйста, вещи, характеризующие "собственное состояние" потока, и скажи, какие из них можно не инициализировать заново в случае реюза убитого потока из тред-пула. И сколько процессорных циклов на этом удастся сэкономить - пять, десять? =)
Понимаешь, когда говорится "создание объекта", в воображении сразу возникает что-то трудоёмкое. На деле чистое создание и инициализация треда без запуска - это

ZeroMemory(threads[threadCount].context, THREAD_CONTEXT_SIZE);
threads[threadCount].affinity = affinity
threads[threadCount].priority = priority
// ещё несколько таких присваиваний
threads[threadCount].context.IP = threadProcAddress;
// и ещё несколько присваиваний
threadCount++;

Не правда ли? Конечно, это тоже занимает какое-то время, но не такое уж большое. Проще надо быть и не использовать высокоуровневых buzzwords там, где речь идёт о каких-то базовых вещах.
Закрытие локов при убийстве треда - это, конечно, довольно трудоёмкая задача. Но её надо делать для всех тредов, и пуловых тоже.

Marinavo_0507

> как-минимум выигрыш по памяти в кол-во раз от кол-ва потоков
столько много этих данных о пользователе, что реально ощутимая экономия?
там что, видео с его участием обрабатывается?
> .net framework
понятно, что если у тебя уже были треды по другим соображениям, то проще именно так сделать
совсем другое дело, когда изначально их нет, а нужно всего лишь придумать механизмы взаимодействия между потоками

Marinavo_0507

> Понимаешь, когда говорится "создание объекта", в воображении сразу
> возникает что-то трудоёмкое.
А как оно в Windows для обычных тредов? Сколько уходит на создание и удаление?

Dasar

> Перечисли мне, пожалуйста, вещи, характеризующие "собственное состояние" потока, и скажи, какие из них можно не инициализировать заново в случае реюза убитого потока из тред-пула
верхушка айсберга:
security
exception-ы
культура
com-овское состояние
тредонезависимое состояние стандартных библиотек (например, C)
очередь сообщений

bleyman

Понятия не имею.
Это, собственно, не очень важно - вряд ли "типичное приложение" будет порождать тысячу тредов в секунду, каждый из которых будет работать меньше миллисекунды и удаляться.
Напомню, что я упомянул тред-пулы, поскольку они являются простым, мощным и эффективным решением проблемы ожидания многих эвентов, реализованным на уровне ядра. Вот и всё.

Marinavo_0507

Это, собственно, не очень важно - вряд ли "типичное приложение" будет порождать тысячу тредов в секунду, каждый из которых будет работать меньше миллисекунды и удаляться.
Веб-сервера и прочие хрени, обрабатывающие короткие внешние запросы, было бы соблазнительно так делать, если бы потоки порождались достаточно быстро.

Marinavo_0507

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

Dasar

> столько много этих данных о пользователе, что реально ощутимая экономия?
1к на пользователя: в основном роли, права
пользователей порядка 50 тыс.

bleyman

security - разве не на уровне процесса хранится?
exception-ы - эээ чо?
культура - один инт?
com-овское состояние - не знаю, не ффтыкал.
тредонезависимое состояние C-ишное библиотеки - а ось тут при чём?
очередь сообщений - инициализируется при первом вызове GetMessage/PeekMessage и как правило нафиг не нужна, так что мимо.

bleyman

Ну да. Смысл в том, что следит за эвентами всегда один тред, а остальные треды вообще тредами не являются, по большому счёту. Насколько я знаю.

Marinavo_0507

> пользователей порядка 50 тыс.
ох
и в каждом треде нужно кешировать все?
сервис, который будет запущен на каждом узле, и будет этим заниматься, не сделать? дополнительная выгода от этого - можно будет, скажем, обновлять собственно приложение без потери кеша; если кеш - действительно такой критичный ресурс, то это важно
а насколько важна согласованность кешей - то есть, если на одном узле данные изменились, как быстро это должны заметить остальные?

Dasar

> security - разве не на уровне процесса хранится?
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-ы - эээ чо?
навешивание обработчиков на разные варианты исключений
> культура - один инт?
начальное значение он из воздуха берет?

Marinavo_0507

> а остальные треды вообще тредами не являются, по большому счёту
как это? им что, стек не нужен?

bleyman

Почитай лучше 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, который периодически просыпается по таймеру и что-то делает, никто тебе на самом деле не гарантирует, что это каждый раз один и тот же тред =)

Dasar

> Насколько я понял - да, не нужен, пока они находятся в состоянии ожидания.
непонятно - на основе чего ты делаешь этот вывод.

Dasar

из этого списка - ожидаемо следует - что пульный тред - это обычный тред, который просто большую часть времени находится в состоянии спячки
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

bleyman

Короче, вот

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

Я тут вижу указатель на секьюрити атрибуты (да, наверное его обработка/проверка занимает какое-то время, но он может быть и нуллом, кстати размер стека, стартовый адрес, произвольный параметр, флаги и возвращаемый параметр. Всё остальное либо создаётся ручками потом, либо забирается из процесса - и что-то аргументы вида "начальное значение он из воздуха берет?" меня забавляют. Нет, не из воздуха. Подозреваю, что присваивание этого начального значения занимает аж целых две ассемблерных инструкции. Я не прав?
И вообще, что ты хочешь доказать, сформулируй чётко, плз.
Да, я согласен, что тред-пул даёт выигрыш ещё и за счёт отсутствия инициализации тредов, окей =) Особенно с учётом того, что на самом деле там может быть два реальных треда на 500 "виртуальных".

Marinavo_0507

> навешивание обработчиков на разные варианты исключений
зачем их заранее навешивать?
наверное, уже после генерации исключения идут по стеку и смотрят, кого вызывать
на самом деле, создавать треды относительно недолго
в линуксе, на типичной тачке 2002г, в новой библиотеке NPTL 10^5 тредов создались и удалились за 2с - это полновесные треды без послаблений

bleyman

из этого списка - ожидаемо следует - что пульный тред - это обычный тред, который просто большую часть времени находится в состоянии спячки
Нет. Из этого списка ожидаемо следует, что пульный тред _можно_ воспринимать так в большинстве случаев, но следует соблюдать некоторые простые правила типа не использовать TLS - довольно странно для "обычного треда", не правда ли?
Далее, "состояние спячки" тут это не вызов внутри коллбэка Wait(1000 а возвращение из коллбэка.
Всё это сделано для того, чтобы ты мог создать 500 как-бы-тредов, которые будут "просыпаться" раз в секунду с интервалом в две миллисекунды и тут же засыпать, а система тебе обеспечила такое поведение с использованием всего двух настоящих тредов.

Dasar

> на самом деле, создавать треды относительно недолго
60 тыс. тактов - это не есть - недолго.

bleyman

мммм...

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 тактов). Пожалуй, дофига, ты прав. Хотя мб я что-то не так сделал.
Как вменяемо переписать этот пример на тред-пулах я что-то не соображу.

Marinavo_0507

> 60 тыс. тактов - это не есть - недолго
относительно недолго
и не 60 тыс, а где-то, наверное 30--40
если с пулами намного быстрее, то согласен, долго

Marinavo_0507

обещанные бенчмарки Linux vs. Windows:

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+

kokoc88

Проверять по таймеру - значит, о завершении узнаешь не сразу.
Кроме того, гонять цикл проверки вместо ожидания - неэффективно.

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

Я искал и те, и другие на практике. В одном окне дебаггера очень приятно видеть два потока, которые хотят поднять "чужую вилку". Можно так же чётко отследить состояния всех объектов в программе. А в случае многопроцессных программ бывает и такое, что один процесс вообще скомпилирован в релиз.
Почему не рассматриваешь другие способы? Экстремальная производительность далеко не всегда нужна, а в остальных случаях рулят сокеты, пайпы, очереди сообщений.

Если не нужна производительность, тогда, конечно, shared memory не важна.
Не смог найти ни одного современного бенчмарка Помню, читал что-то.
Самому, что ли, на досуге проверить? Правда, нужно чтобы виндовый код кто-нибудь написал.
С апачем проблема в другом, он от переключения контекста не сильно зависит (мало их там и от времени создания потока тоже (редко оно происходит, там же пул). Например, про знаменитый бенчмарк в 2000г Apache vs. IIS помню, ту проблему почти сразу и пофиксили.
Бенчмарки искать бесполезно. Во-первых, почти наверняка найдутся только старые. Во-вторых, они кем-нибудь куплены. Я знаю, что до появления поддержки потоков в ядре linux, апач под винду рулил апач под линукс. Потом я перестал интересоваться этим вопросом. Можно покопать http://www.spec.org/ Впрочем, ещё не сложно отгуглить что-нибудь типа
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 будет всё хорошо. Хотя в целом я понял твой довод и согласен с ним. Думаю, сюда же можно добавить второй, с которым я бы согласился: утечки памяти и прочих ресурсов.

Marinavo_0507

аналогичный бенчмарк на тредах
число итераций пришлось увеличить до 1000, иначе слишком быстро работало
#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с.

Marinavo_0507

Если не нужна производительность, тогда, конечно, shared memory не важна.
Очень часто производительность нужна, но узкое место вовсе не в обмене между потоками.
Если процессы в твоей задаче делают какую-то короткую работу и выходят, то задача опять же решается тред пулами. Если они делают что-то реально долгое, такое, что окупается многопроцессность, то не важно, узнаешь ты о завершении сейчас или через одну секунду.
Бывает, что некоторые работают долго, а некоторые быстро, и заранее не узнаешь, сколько будет работать. Например, получение или отправка письма по SMTP. Кроме того, в этом случае сторонние плагины, написанные на самых разных языках, могут работать, так что безопасностью JVM не отмажешься.
Я считаю большим минусом, если самый быстрый механизм синхронизации в ОС работает с потоками так же медленно, как и с процессами.
В данном случае - одинаково быстро - вот это уже плюс.
Ровно как и то, что переключение между путём выполнения и контекстом выполнения занимает одинаковое время.
Время не одинаковое. Треды переключаются ещё быстрее, хотя бы потому, что трансляцию страниц подменять не надо. Смотри бенчмарк.
Apache 1.3 ценится вовсе не за экстремальную производительность (для этого есть более другие сервера а за корректность, надёжность и безопасность. Модель prefork это всё обеспечивает, при приемлемой производительности (скрипты на php, perl всё равно работают гораздо дольше, чем процессы переключаются)
Да не важно, как оно происходит, Java VM тут не при чём. Она работает с потоками на уровне ОС.
Зато JVM отвественна за распределение памяти внутри приложения, следовательно, за эффективное использование процессорного кеша.

kokoc88

А вот если ты задумаешься над своей фразой про "повторное использование и экономию на инициализации", то поймёшь, что она на самом деле бессмысленная =)
Ты опять лезешь в дискуссию, либо не изучив тему, либо не поняв и не опробовав на практике. Погугли и найдёшь примерно такие статьи:
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
Всё остальное, о чём ты пишешь, представляет собой следствие.

kokoc88

Очень часто производительность нужна, но узкое место вовсе не в обмене между потоками.

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

Вот именно, что не плюс, а минус. Потому что точно можно создать объект синхронизации, который будет с потоками работать быстрее, чем с процессами. В винде такой есть. И я не верю, что в *nix'ах, при их стремлении быть лучше винды хотя бы в этих вопросах, нет таких механизмов синхронизации.
Время не одинаковое. Треды переключаются ещё быстрее, хотя бы потому, что трансляцию страниц подменять не надо. Смотри бенчмарк.

Ты сам себе противоречишь. То у тебя процессы переключаются так же, как потоки. То потоки начинают переключаться ещё быстрее. Определись с чем-то одним и ответь, что будет производительнее: потоки или процессы?
Зато JVM отвественна за распределение памяти внутри приложения, следовательно, за эффективное использование процессорного кеша.
Мне всегда казалось, что ОС сама отвечает за оптимизацию кэширования кусков памяти. Поэтому я не понимаю, почему вдруг Java машина должна за этим следить? А как, например, за этим могла бы уследить программа на Си?

Marinavo_0507

Я не вижу, как в поставленной задаче теряется производительность при применении цикла с проверкой процессов раз в секунду.
Например, нужно сразу знать, что письмо ушло, чтобы следующее из очереди на этот же маршрут достать (у продвинутых серверов отдельные хитрые очереди и лимиты на каждый маршрут). А рядом, скажем, спам-фильтр, который иногда 10мс на письмо тратит, а иногда 10с. Раз в 10мс проверять завершение - неэффективно. Раз в секунду - будешь ждать лишнюю секунду, если всё быстро проверится - почта будет сильно дольше уходить.
Потому что точно можно создать объект синхронизации, который будет с потоками работать быстрее, чем с процессами. В винде такой есть.
Что есть в винде? За счёт чего нельзя процессы так же быстро синхронизировать? Давай попробуем сравнить на практике производительность, так же как создания процессов/тредов выше.
Ты сам себе противоречишь. То у тебя процессы переключаются так же, как потоки. То потоки начинают переключаться ещё быстрее. Определись с чем-то одним и ответь, что будет производительнее: потоки или процессы?
Обычные процессы в Linux, по моим сведениям, создаются и переключаются примерно так же быстро, как обычные потоки в Windows.
Обычные потоки в Linux (то есть POSIX threads) - ещё в несколько раз быстрее.
Всякие извращения (легковесные потоки, которые целиком в юзерспейсе, или там пресловутые не-совсем-настоящие-потоки из пула) - может быть, ещё быстрее.
Про создание процессов и тредов - смотри результаты выше.
Про переключение - можно попробовать добавить дополнительные переключения.
Мне всегда казалось, что ОС сама отвечает за оптимизацию кэширования кусков памяти.
Нет, традиционная ОС этого сделать не может. То есть может для структур ядра, но не для данных приложения.

kokoc88

Например, нужно сразу знать, что письмо ушло, чтобы следующее из очереди на этот же маршрут достать (у продвинутых серверов отдельные хитрые очереди и лимиты на каждый маршрут). А рядом, скажем, спам-фильтр, который иногда 10мс на письмо тратит, а иногда 10с. Раз в 10мс проверять завершение - неэффективно. Раз в секунду - будешь ждать лишнюю секунду, если всё быстро проверится - почта будет сильно дольше уходить.

Это уже задача для потоков, и реализация с тред пулом (или mixed) порулит любую другую.
Что есть в винде? За счёт чего нельзя процессы так же быстро синхронизировать? Давай попробуем сравнить на практике производительность, так же как создания процессов/тредов выше.

Как-то вы странно сравниваете производительность процессов и потоков выше. Ставь реальную задачу, а не пустые циклы. (Так и не нашёл, где у тебя дочерний процесс трогает память.)
Про создание процессов и тредов - смотри результаты выше.

Какие результаты? Не вижу ничего, чтобы можно было считать результатом. Ставь реальную практическую задачу.
Нет, традиционная ОС этого сделать не может. То есть может для структур ядра, но не для данных приложения.

Замечательно, так в чём разница между Java и тем же Си в этом вопросе?

Marinavo_0507

Это уже задача для потоков, и реализация с тред пулом (или mixed) порулит любую другую.
Естественно, порулит, если на Java по-другому и не сделать.
Ставь реальную задачу, а не пустые циклы.
Есть такое понятие - микро-бенчмарк. Это было оно, вопросы - к , я только портировал под POSIX. Лишнего дня для кодирования реальной задачи у меня, увы, нет пока.
Замечательно, так в чём разница между Java и тем же Си в этом вопросе?
На Си можно вручную правильно сгруппировать данные, написав свой аллокатор. Кроме того, мы сравниваем уже не Java и C, а процессы и потоки. Так вот, при отдельных адресных пространствах такой проблемы просто нет.

kokoc88

Естественно, порулит, если на Java по-другому и не сделать.
Java тут не при чём: порулит на любом языке.
Есть такое понятие - микро-бенчмарк. Это было оно, вопросы - к , я только портировал под POSIX. Лишнего дня для кодирования реальной задачи у меня, увы, нет пока.
По этим "бенчмаркам" вообще ничего не ясно для проведения сравнения работы потоков на двух разных ОС.
На Си можно вручную правильно сгруппировать данные, написав свой аллокатор. Кроме того, мы сравниваем уже не Java и C, а процессы и потоки. Так вот, при отдельных адресных пространствах такой проблемы просто нет.
В Java размещение объектов и работа с памятью уже грамотно реализованы. На Си ты никогда не сгруппируешь данные так, чтобы у тебя всё и везде было хорошо. (Если только не напишешь какую-нибудь маленькую программу без реального практического смысла.) При отдельных адресных пространствах возникает такая же проблема, почему нет?

Marinavo_0507

По этим "бенчмаркам" вообще ничего не ясно для проведения сравнения работы потоков на двух разных ОС.
Известна производительность создания+удаления потоков.
В Java размещение объектов и работа с памятью уже грамотно реализованы.
Покажи хотя бы в рекламе строчку, где написано про предотвращение ненужных прыжков линий кеша.
На Си ты никогда не сгруппируешь данные так, чтобы у тебя всё и везде было хорошо.
Обычно выделяют узкое место профилированием, и оптимизируют его.
При отдельных адресных пространствах возникает такая же проблема, почему нет?
Когда поймёшь суть проблемы, этот вопрос отпадёт сам.

kokoc88

Известна производительность создания+удаления потоков.

Ты бы взял на работу программиста, который бы решал многопоточные time critical задачи таким образом? (Каждый раз создавая и удаляя поток.) Я могу переписать ваш код так, что он вычислительно сделает всё то же самое. Но будет работать быстрее.
Обычно выделяют узкое место профилированием, и оптимизируют его.

То же самое ты можешь сделать в Java.
Когда поймёшь суть проблемы, этот вопрос отпадёт сам.

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

Marinavo_0507

Ты бы взял на работу программиста, который бы решал многопоточные time critical задачи таким образом? (Каждый раз создавая и удаляя поток.)
Если такое решение проще, а производительность приемлема - значит, программист сделал правильно. В ряде случаев так оно и бывает. Premature optimization ... продолжение знаешь?
То же самое ты можешь сделать в Java.
Профилировать - сможешь, а свой аллокатор - уже сложнее.
А если адресные пространства разные, то и оптимизировать ничего не надо.
многопроцессный http сервер
посмотри на nginx, например
только апач всё равно - самый популярный, несмотря на меньшую производительность, и я объяснил почему

kokoc88

Если такое решение проще, а производительность приемлема - значит, программист сделал правильно. В ряде случаев так оно и бывает. Premature optimization ... продолжение знаешь?

Тогда я не понимаю, о каком сравнении производительности идёт речь, если она не нужна? Программист всё равно сделал неправильно. Это как поставить список туда, где нужен мэп. Потом получится так, что в этом списке появляется всё больше элементов, и программа умирает.
А если адресные пространства разные, то и оптимизировать ничего не надо.

Я уже сказал, что накладки по использованию процессов превзойдут все такие оптимизации. Зачем смотреть на nginx - неудачный пример, т.к. у него нет multithreaded option. Я забыл отметить, что сравнивать надо одинаковые программы.

Marinavo_0507

Это как поставить список туда, где нужен мэп.
Пока у твоей фирмы 10 клиентов, их можно хранить в списке. Когда станет 10000, и список станет подтормаживать, программист реализует более сложную структуру, с учётом данных профилирования. А пока клиентов мало, мало и денег, а актуальных задач много - пусть программист займётся чем-нибудь полезным.
Я забыл отметить, что сравнивать надо одинаковые программы.
Если перейти от процессов к потокам при сохранении того же уровня безопасности,надёжности и поддерживаемости, дизайн изменится, и программы перестанут быть одинаковыми.

kokoc88

Пока у твоей фирмы 10 клиентов, их можно хранить в списке. Когда станет 10000, и список станет подтормаживать, программист реализует более сложную структуру, с учётом данных профилирования. А пока клиентов мало, мало и денег, а актуальных задач много - пусть программист займётся чем-нибудь полезным.
Это в корне неверный подход к программированию. Хотя твой пример никак не отображает того, что я сказал. Хороший программист в случае, когда нужен map, пишет именно map, а не list.
Если перейти от процессов к потокам при сохранении того же уровня безопасности,надёжности и поддерживаемости, дизайн изменится, и программы перестанут быть одинаковыми.

Все вэб серверы, имеющие бенчмарк и у которых есть опции многопоточного/многопроцессного выполнения, имеют более высокую производительность на потоках.

Marinavo_0507

Это в корне неверный подход к программированию.
Религиозный вопрос. Многие считают, что верный.
Все вэб серверы, имеющие бенчмарк и у которых есть опции многопоточного/многопроцессного выполнения, имеют более высокую производительность на потоках.
Какие ты знаешь, кроме апача и надстроек на его основе?
Производительность растёт, а надёжность и корректность падают. И модули переписывать приходится, так как API новое пришлось делать. В mod_php, кажись, до сих пор не все глюки отловили после переписывания.
Ещё ты хотел рассказать про чудесные механизмы синхронизации потоков в Windows.

kokoc88

Производительность растёт, а надёжность и корректность падают.
Надёжность падает как раз у тех, кто списки использует вместо потоков.
А вообще я считаю разговор законченным со следующим заключением:
- Процессы лучше потоков только в плане memory/resource leak.
- В плане безопасности вопрос не ясен, потому что я не знаю, на каком уровне реализована безопасность потоков в *nix. (А в винде она есть.)
- Java использует native код для работы с потоками, процессами и прочее. Поэтому никакого преимущества Си не имеет.
- Была упомянута чисто теоретическая возможность оптимизации многопроцессных программ под кэш. Опять же, вопрос до конца не ясен и не описан.
- Запуск/останов потоков в линуксе в некотором приближении быстрее, чем в винде.
- Переключение процессов в линуксе вроде как быстрее, чем в винде.
- В линуксе отсутствует примитив синхронизации, работающий на уровне потоков, вследствие чего имеющий более высокую производительность.
- Одинаковые программы, написанные под потоки и под процессы, имеют разительно большую производительность в варианте с потоками.
- Дебаг многопоточных программ гораздо проще/легче, чем дебаг многопроцессных.

Marinavo_0507

> А вообще я считаю разговор законченным
Заметно, что кончились аргументы.
От повторения недоказанных суждений они ведь не станут верными.

Ivan8209

>> Это в корне неверный подход к программированию.
> Религиозный вопрос. Многие считают, что верный.
Не знаю ни одного примера с противоположной стороны,
ты можешь показать хотя бы парочку, чтобы было "многие," а не "все?"
---
...Я работаю антинаучным аферистом...

kokoc88

Заметно, что кончились аргументы.
От повторения недоказанных суждений они ведь не станут верными.
Нет, заметно, что кто-то перешёл на личности.
Что касается аргументов, они все названы. Ссылки на сайты и цитаты я привёл. Писать программу для реального практического теста ты отказался. Давил всё время на security процессов, как на огромное преимущество. Я уже сказал, что в винде потоки могут работать с разным security, а поэтому твои аргументы о безопасности применимы разве что к линуксу, если в нём нету такого механизма. Про кэш: во-первых, ничего не объяснил; во-вторых, не привёл никаких доказательств, что оно вообще возможно, а для меня философский взгляд не является аргументом.

Ivan8209

> - Java использует native код для работы с потоками, процессами и прочее.
> Поэтому никакого преимущества Си не имеет.
В конце концов, всё сводится к native code,
только при этом объёмы native code очень разные.
Я не вижу причин, почему ява хотя бы сравнима с сями.
> - Дебаг многопоточных программ гораздо проще/легче, чем дебаг многопроцессных.
"От обоснуя и слышу."
---
...Я работаю антинаучным аферистом...

Ivan8209

> Писать программу для реального практического теста ты отказался.
Видел код только -а, но не твой.
---
"Я знаю правду! Все прежние правды --- прочь!"

Marinavo_0507

Я уже сказал, что в винде потоки могут работать с разным security
Защиты памяти-то нет, так что это только для удобства (имперсонирование, например а не для защиты.
Про кэш: во-первых, ничего не объяснил; во-вторых, не привёл никаких доказательств, что оно вообще возможно
А ты бы спросил по-хорошему, или в гугле бы поискал. А то пока, похоже, только ты не знаешь, что это за проблема. Объясню попозже, раз ты не в теме совсем.
Писать программу для реального практического теста ты отказался.
Напиши сам, а я портирую на Linux, с тредами и с процессами, если найду время на выходных.
Сам-то ты не назвал волшебные средства синхронизации потоков в винде, которые быстрее линуксовых.

kokoc88

"От обоснуя и слышу."
Как показала жизненная практика, ты умеешь:
- Пиздеть.
Как показала жизненная практика ты не умеешь:
- Программировать.
- Понимать, как работают программы.
- Считать до двух.
А посему тебе следует:
- Кликнуть сюда http://www.diablo.jino-net.ru/ и любоваться.

Marinavo_0507

Одинаковые программы, написанные под потоки и под процессы, имеют разительно большую производительность в варианте с потоками.
Надо понимать, что в случае веб-сервера разница видна только в специально придуманных тестах. На практике же, например, многие админы считают, что второй апач - отстой, и юзают 1.3. Потому что 1% производительности менее важен, чем надёжность и безопасность, с которыми у apache 2 до сих пор не очень.
Даже на хостингах, где производительность очень важна. Навскидку - masterhost, zenon. Также 1.3 стоит на yandex.ru и mail.ru - тоже нагрузка там неслабая, наверное.

kokoc88

Защиты памяти-то нет, так что это только для удобства (имперсонирование, например а не для защиты.

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

Я не в теме совсем, почему это работает с процессами и не работает с потоками. Или хотя бы почему при этом многопроцессная программа решает многопоточную. И я не думаю, что возможна такая оптимизация, разве что в очень редких случаях.
Надо понимать, что в случае веб-сервера разница видна только в специально придуманных тестах. На практике же, например, многие админы считают, что второй апач - отстой, и юзают 1.3. Потому что 1% производительности менее важен, чем надёжность и безопасность

Бенчмарки сейчас довольно комплексные, можно посмотреть конкретные интересующие данные. Опять же, производительность растёт не на 1%, а от 50% и выше.

Marinavo_0507

Для защиты в том числе. Так что я всё равно не считаю твой довод таким уж значительным.
Если тред А имеет доступ к (почти) всей памяти треда B, то он (почти всегда) может выполнить что угодно с теми правами, которые есть у B.
Если нужна безопасность, приложение проектируют таким образом, чтобы модули общались между собой только через чётко прописанные интерфейсы, а по-другому не могли. Это, как бы, на первом занятии по теме должны рассказывать.
Как удостовериться, что модули не могут общаться в обход интерфейсов? Если они в отдельных процессах, то за этим следит ядро. А если у них общая память, то один другому может испоганить любые данные, если захочет (или случайно, тогда это угроза надёжности, а не безопасности).

sergey_m

Дебаг многопоточных программ гораздо проще/легче, чем дебаг многопроцессных.
Чем посылать Контру, лучше бы действительно обосновал это очень и очень спорное утверждение.

sergey_m

> Также 1.3 стоит на yandex.ru и mail.ru - тоже нагрузка там неслабая, наверное.
+rambler.ru

Marinavo_0507

rambler.ru я посмотрел, он сказал, что там nginx

sergey_m

> rambler.ru я посмотрел, он сказал, что там nginx
Спереди nginx, сзади apache.

Marinavo_0507

А что у зенона спереди, не знаешь? Раньше (в 2000г) вроде сквид был, обратил внимание на характерные ошибки.

kokoc88

Чем посылать Контру, лучше бы действительно обосновал это очень и очень спорное утверждение.
Я уже обосновал его выше: ты видишь сразу все данные, у тебя запущен один дебаггер, ты одним действием можешь останавливать и запускать программу, ты можешь поставить брейкпоинт (при дебаге многопроцессных программ если один процесс после fork делает exec, то тебе придётся дебаггером аттачиться к новому процессу да и далеко не все дебаггеры отслеживают запуск новых процессов.

sergey_m

Я уже обосновал его выше: ты видишь сразу все данные, у тебя запущен один дебаггер, ты одним действием можешь останавливать и запускать программу, ты можешь поставить брейкпоинт (при дебаге многопроцессных программ если один процесс после fork делает exec, то тебе придётся дебаггером аттачиться к новому процессу да и далеко не все дебаггеры отслеживают запуск новых процессов.
В многопроцессной системе не может быть такой ошибки, в которую вовлечены два процесса. Поэтому нет необходимости запускать два дебаггера. Зато в однопроцессорной многопоточной системе могут быть ошибки такого рода, которые очень тяжело дебажить.

kokoc88

В многопроцессной системе не может быть такой ошибки, в которую вовлечены два процесса. Поэтому нет необходимости запускать два дебаггера. Зато в однопроцессорной многопоточной системе могут быть ошибки такого рода, которые очень тяжело дебажить.
А два процесса и два каких-нибудь мьютекса, которые лочатся в разном порядке - это не ошибка?

kokoc88

Да, я тебя прекрасно понял, когда ты описал эту проблему в первый раз. И как я написал выше, довод считаю более-менее убедительным, и только. Я на практике никогда не занимался этой проблемой, но думаю, что в инете есть статьи, как правильно делать безопасные многопоточные программы. В конечном итоге, можно применять смесь процессов и потоков, возлагая на потоки все функции программы, а на процесс - безопасность памяти.

Dasar

В многопроцессной системе не может быть такой ошибки, в которую вовлечены два процесса
а куда делись гонки?

Dasar

опять же не понятно, почему не может быть одновременного обращения к каким-то общим ресурсам: файлам, портам и т.д.

Dasar

> число итераций пришлось увеличить до 1000, иначе слишком быстро работало
код - не идентичен оригинальному коду, и понятно, что скорее всего поэтому работает быстрее.

Marinavo_0507

> код - не идентичен оригинальному коду
чего он не делает?

Dasar

начинает закрывать потоки, не дожидаясь полного окончания всех тредов

Marinavo_0507

ну а что я сделаю, если в POSIX ожидание и закрытие совмещены в одном вызове? главное, что в конце все треды завершились (то есть дело сделано)
если windows лишнего вызова требует, я не виноват

Marinavo_0507

Обещанный ликбез про прыгающие строчки кеша.
1. Как устроен кеш? Обмен между ним и памятью идёт по строчкам. То есть, вся память поделена на строчки, некоторые из которых могут присутствовать в кеше. Размер строчки зависит от конкретной модели процессора, порядка 16..64 байт вроде бы обычно. Когда процессор читает из памяти, целая строчка попадает в кеш. Когда процессор пишет в память, целая строчка отмечается как "грязная", и в конце концов некоторое время копируется в память.
2. А если процессоров несколько? Обычно у каждого процессора свой кеш. То есть, одна и та же строчка может присутствовать сразу в нескольких кешах. Если все они "чистые", то всё просто. А вот если какой-то процессор захочет туда что-то записать, ему нужно сообщить другим, что их варианты стали неверны. Для этого он сначала получает эксклюзивный доступ к этой строчке, сообщая остальным процессорам, что они должны вычистить её из своих кешей. Потом проводит запись. Если на другом процессоре опять понадобится эта строчка, то ему нужно будет взять её либо из памяти, либо из кеша первого процессора (в зависимости от реализации) - это примерно на порядок дольше, чем из L2-кеша. Если это действительно разделяемые данные, то никуда не денешься - нужно проводить межпроцессорный обмен всё равно.
3. Какая в связи с этим возможна подстава? Вроде бы всё понятно. Если данные нужны только на одном процессоре, то он спокойно с ними работает. Если на нескольких сразу, то при каждой модификации происходит межпроцессорный обмен. Однако, может оказаться так, что в одной строчке кеша оказались семантически разные объекты данных. Пусть строчка делится между объектами A и B. Пусть есть потоки 1 и 2, выполняющиеся в данный момент на разных процессорах. Пусть поток 1 активно работает с объектом A, а поток 2 - с объектом B. Если, скажем, поток 1 изменил объект A, то объект B пропал из кеша, и поток 2 будет грузить его заново. Таким образом, строчка прыгает между процессорами, создавая паразитный трафик на шине и приводя к лишним задержкам. Запросто может оказаться, что неудачно написанный код на двух процессорах будет исполняться медленнее, чем на одном.
4. Как с этим бороться? Нужно делать так, чтобы в одной строчке не оказывались разные объекты, с которыми идёт работа в разных тредах.
Конкретнее, известны такие рецепты:
a) истинно разделяемые объекты часто имеет смысл выравнивать по границе строчки: если, это мьютексы какие-нибудь, то нельзя их ставить в одну строчку, а то возможны лишние задержки при блокировке, если данный мьютекс не занят, а занят совершенно левый, соседний; однако, есть и исключения - если это целый массив мьютексов, например, к которому идёт доступ по результатом хеш-функции, то такие коллизии маловероятны, их можно и упаковать поплотнее;
б) если объект (в основном) приватен для какого-то потока, то рядом с ним надо класть подобные же ему; совсем хорошо, если они и использоваться будут близко во времени;
в) если в разделяемой структуре есть поля, часто изменяемые, а есть (почти) постоянные, то нужно сгруппировать первые отдельно от вторых, чтобы прыгало поменьше строчек;
... наверняка я не все трюки знаю.
Соответственно, идеальный аллокатор должен иметь информацию о том, как будут использоваться создаваемые объекты, и грамотно её учитывать.
Можно, например, предположить, что если объекты создаются в каком-то треде, то там же в основном и будут использоваться. Но тут нас ждёт жестокий облом с разделяемыми объектами: их ведь тоже в каком-то треде нужно создавать, а аллокатор не в курсе. Кроме того, может быть удобно передавать объекты в "долговременное пользование" другим потокам.
Получается, не обойтись без подсказок со стороны программиста. На C/С++ для ключевых объектов можно сделать свой аллокатор, и ручками там всё расставить. На Java - облом, никак не передать подсказку.
А что с отдельными адресными пространствами? А там всё хорошо, так как приватные объекты явно отделены от разделяемых (последние сидят либо в общей памяти, либо как-то явно передаются через какой-нибудь IPC). Приватные объекты можно выделять без боязни стандартным аллокатором, без подсказок. Подсказки нужны только для разделямых объектов.
В общем адресном пространстве тоже можно так сделать, конечно (если стандартный аллокатор имеет выделяет в каждом потоке из отдельного пула, и если при этом можно свой аллокатор реализовать для разделяемых объектов). Только вот при проектировании многопроцессного приложения разработчик сразу учитывает эти вещи, а в многопоточном - может какой-нибудь рефакторинг понадобиться.

sergey_m

В FAQ что ли засунь, если всё правда.

Ivan8209

Я, похоже, знаю, как должен работать идеальный аллокатор.
64 октета --- это всего лишь 16 32-разрядных слов, то есть не так уж и много.
А если 16 --- и того меньше.
Покупайте больше памяти!
---
...Я работаю антинаучным аферистом...

kokoc88

Какая в связи с этим возможна подстава?

Знаешь, чтобы происходило всё, о чём ты написал, надо реально постараться. Думаю, что такая проблема возможна, но эта возможность в реальной практике будет очень маленькой. Объекты можно ещё располагать на стеке, т.к. судя по выкладкам проблема связана с частым использованием стоящих рядом маленьких объектов (или конца одного - начала другого, что менее вероятно) в разных потоках.
Только вот при проектировании многопроцессного приложения разработчик сразу учитывает эти вещи, а в многопоточном - может какой-нибудь рефакторинг понадобиться.
Как из всего этого следует, что два процесса, работающих с одним куском shared memory, полностью застрахованы от этой проблемы? Как я понял, никак не следует. Да и передача разделяемых объектов через сокет или пайп по времени будет дольше, чем промахи кэша. В общем, я примерно понял, что за проблема, но считаю, что плюсов от использования потоков больше, чем теоритеческий шанс нарваться на проблемы с кэшированием.

Ivan8209

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

Marinavo_0507

Думаю, что такая проблема возможна, но эта возможность в реальной практике будет очень маленькой.
Зависит от приложения.
В ядре Линукса (оно сильно многопоточное, с (почти) общим адресным пространством и кучей разделяемых объектов) то и дело что-то оптимизируют по этому поводу: поля переставляют, выравнивание добавляют, per-cpu-пулы вводят.
А если мало разделяемых объектов (на практике это гораздо чаще бывает то будет невесело, если они случайно перемешаются в куче. На стеке всё не выделишь, кучу всё-таки не зря придумали.

Ivan8209

> А если мало разделяемых объектов (на практике это гораздо чаще бывает то
> будет невесело, если они случайно перемешаются в куче.
Почему?
Разве трансляцию страниц отменили?
---
...Я работаю антинаучным аферистом...

Marinavo_0507

> 60 тыс. тактов - это не есть - недолго.
ну на самом деле всего лишь в несколько раз медленнее, чем IP-пакет обработать
то есть, полновесное TCP-соединение открыть и закрыть получается дольше, чем тред удалить и создать, так что я понимаю тех, кто пишет сервера по принципу один тред на соединение (особенно любителей Windows и Solaris, в которых треды довольно быстрые, а TCP-стек - не особо).

kokoc88

Зависит от приложения.
В ядре Линукса (оно сильно многопоточное, с (почти) общим адресным пространством и кучей разделяемых объектов) то и дело что-то оптимизируют по этому поводу: поля переставляют, выравнивание добавляют, per-cpu-пулы вводят.
Ядро ОС - сильно частный случай, и я нигде в этом треде не спорил, что Си+асемблер лучше всего подходят для этого. И ещё я писал, что ОС должна уметь работать с кэшем. И помогать другим программам, например, выравнивая размещённые в куче куски памяти.

Marinavo_0507

Ядро ОС - сильно частный случай
Я не смог навскидку придумать такие же сложные многопоточные приложения.
Всё, что я знаю, или многопроцессное (а то и распределённое либо однопоточное и не масшабируется на несколько процессоров.
Оставить комментарий
Имя или ник:
Комментарий: