Ceki Gülcü (author of Log4j) пишет в своем блоге про крайне неравномерные (максимально неравномерные) результаты при использовании synchronized:
"
runnable[0]: counter=1002
runnable[1]: counter=0
runnable[2]: counter=0
runnable[3]: counter=0
runnable[4]: counter=0
"
При том, что ожидалось "примерно по 200".
Это к тому, что
1) симметричные уравнения и симметричные граничные условия могут давать асимметричные решения;
2) unfair примитивы синхронизации МОГУТ, но НЕ ОБЯЗАНЫ обеспечивать "статистическую честность".
P.S. Кстати, всегда интересовало что говорят заказчику, если в проекте используются unfair примитивы синхронизации, которые могут привести к такому поведению (фактически livelock для некоторых потоков), но во всех тестах ведут себя "хорошо". Что будет если в какой-то момент времени заказчик потеряет деньги из-за таких вот особенностей поведения ПО? Т.е. считать ли проект "выполненным/готовым"?
воскресенье, 20 декабря 2009 г.
Подписаться на:
Комментарии к сообщению (Atom)
Ceki Gülcü написал бред.
ОтветитьУдалитьЕсли он хотел посмотреть до куда досчитает thread-private counter в каждом трэде, то не надо было делать synchronized(LOCK){counter++}.
Если же он хотел посчитать throughput для shared counter-а(до куда он досчитается в условиях конкурентной среды), то надо было сделать counter shared-ом.
На самом деле он доказал что biased locking рулит: пока в jdk1.5 thread-scheduler "даёт играеться" в concurrency всем желающим thread-ам(синхронизирует их working memory с main memory, т.е. делает context switching) и тем самым накручивает counter только до 201, в jdk1.6 - только "hot" thread-ы(чей working memory еще тёпленький) получает LOCK, и тем самым counter накручивается до 1002!
Я полагаю Ceki рассматривал немного иную ситуацию:
ОтветитьУдалитьдопустим Вы создаете экземпляр класса
class MyConsole {
public void synchronized printIt(String msg) {...}
}
это будет Singleton для Вашего сервера. Сервер использует Thread-per-Request. То может оказаться, что весьма симметрично делящий процессорное время между потоками шедулер в осутствии вызовов "synchrinized printIt(....)" из потоков пользователей будет очень неравномерно делить время если этот метод вызывать.
Т.е. обеспечивая свойства Visibility и Mutual Exclusion лочка типа synchronized приводит к совершенно неприемлемым перекосам и, соответственно, в большинстве случаев ее просто необходимо заменить на, скажем, j.u.c.ReentrantLock(true).
Т.е. выходит что любой Singleton защищенный synchronized неприемлем для Thread-per-Request.
ОтветитьУдалитьПример такого синглетона из JDK:
ОтветитьУдалитьjava.util.logging.LogManager {
public static java.util.logging.LogManager getLogManager() {...}
public synchronized java.util.logging.Logger getLogger(java.lang.String s) {...}
}
т.е. вызов в потоках
LogManager.getLogManager().getLogger(...) приводит к "перекосам" в шедулере.
Чтобы случился перекос нужно иметь код по типу:
ОтветитьУдалитьnew Runnable {
void run() {
for(;;) {
// NO CODE HERE
console.printIt(s);
// NO CODE HERE
}
}
}
Т.е. все thread-ы должны только того и делать что писать в консоль. Это нереально. Обычно все имеют дело с кодом типа:
new Runnable {
void run() {
for(;;) {
console.printIt("logging in " + env);
logService.login(env)
console.printIt("logged! " + env);
}
}
}
А вот еще перекос:
ОтветитьУдалить// WRITER
new Runnable() {
void run() {
for(;;) {
oldStyleCounter.incr();
}
}
}
// READER
new Runnable() {
void run() {
for(;;) {
oldStyleCounter.get();
}
}
}
Но стоит добавить Thread.sleep() в одном из них и перекос исчезнет.
Т.е. говоря строго академически то да - будет "перекос", но если практичнее посмотреть - то его не будет.
Этот комментарий был удален автором.
ОтветитьУдалитьХорошо, но что будет если синхронизирован метод синглетона, который работает долго. Скажем некоторый org.apache.log4j.FileAppender у которого setBufferedIO(false) или соотношение BufferSize и сообщения(большое, скажем очень объемный stacktrace) таково, что каждое обращение doAppend(LoggingEvent event) будет вызывать запись в файл?
ОтветитьУдалитьКак по Вашему - будет ли перекос в таком случае? Окажется ли что, операция логирования, по факту, доступна только одному потоку?
----
Я согласен, что ситуация немного синтетическая. Просто я, как программист такой "кривой" системы или системы в таком "кривом" режиме ожидаю, что все потоки будут "тормозить" "примерно равномерно", но окажется, что прогресс наблюдается исключительно у одного потока.
----
Заранее согласен, что синхронизировать долгий метод синглетона - зло. Но вдруг он долгим стал при некоторых неожиданных условиях. В моем примере с FileAppender дефолтный размер буфера - 8К. Вот допустим, что полезли огромные стектрейсы из всех потоков(соизмеримые с 8К), но в логе будут только от одного потока - того, что первый начал писать.
Согласен, что ситуации синтетические, но лично для меня и результаты немного неожиданны. Ожидал, что программисты synchronized сделают некоторую "статистическую неабсолютную нечестность", т.е. 1)на колве обращений стремящемся к бесконечности и конечном кол-ве потоков(К) каждый получит долю не стремящуюся к нулю (не обязательно стремящуюся к 1/К), 2)на колве обращений стремящемся к бесконечности и конечном кол-ве потоков(К) никакой поток не будет генерить неограниченно растущих последовательных цепочек.
ОтветитьУдалить