Информация об изменениях

Сообщение Re[18]: Почему Эрланг от 14.06.2015 9:15

Изменено 14.06.2015 9:25 vdimas

Здравствуйте, Ikemefula, Вы писали:

I>Кстати, вопрос такой — ты в курсе, что Windows до определенной версии поддерживала только кооперетивную многозадачность ?

I>Ты в курсе, что уже тогда в ней были и семафоры, и муткесы.

Семафоры и мьютексы более использовались для сигналинга м/у процессами, внутри одного процесса обычно пользовали CRITICAL_SECTION.

Ну и еще в Windows 16 работали перывания прямо в юзверском коде ))
Поэтому, если твоя прога сидела на прерываниях, то эти прерывания тоже как-то обыгрывались: либо ресурсы защищались теми же критическими секциями, либо для какого-то участка кода запрещались прерывания.


I>Как так, ведь в кооперативной многозадачности "гонок нет"


В Windows 16 (если ты о ней) кооперативный шедулер срабатывал на КАЖДЫЙ вызов системного АПИ, наверно в этом дело?

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

I>Все очень просто — ты хочешь съехать с кооперативной многозадачности.


Ы?

I>У тебя вытесняющая многозадачность, а я говорю про кооперативную. Представь себе, гонки это свойство любой многозадачности, а не только вытесняющей.


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

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

В базе жабаскрипта есть вполне детерминированная однопоточная модель вычислений — ты можешь пользоваться знаниями об этом.

Например, в случае вытесняющей многозадачности, для сериализации доступа к разделяемому ресурсу ты должен писать как-то так:
[ccode]
try {
Monitor.enter(guard1);
resource.access1(); // (1)
resource.access2(); // (2)
} finally {
Monitor.Exit(guard1);
}
[ccode]
Что логически эквивалентно сишному:
[ccode]
try {
RaiiGuard g(criticalSection1);
resource.access1(); // (1)
resource.access2(); // (2)
}
[ccode]

Но в жабаскрипте для обеспечения детерминированности достаточно, чтобы м/у точками (1) и (2) не произошло переключения задач. Т.е. не вставляй м/у точками (1) и (2) никаких await/yield и всех делов.

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


I>У тебя плохой пример. Раздели чтение переменной и запись на две операции. Внезапно, обнаруживаем инвариант — поток затирает ровно то значение, которое перед этим прочёл.


Да, но где здесь недетерминированность?
Вот у меня код на жабаскрипте:
var x = 1;
x = x * 10;
x = x - 5;
[java]
В условиях детерминированности ты можешь в своей программе полагаться на значение x. Это фундаментальное св-во, лежащее в основе вcего программирования, что ты можешь полагаться на логику своей собственной программы.

Теперь другой пример:
[java]
var x = 1;

async function f1() {
  var x1 = x * 10;
  await someAsyncOp(); // (1)
  x = x1 - 5;
}
[java]

Пример тоже полностью детерминированный. Ты можешь полагаться на значение локальной переменной x1, но уже не можешь полагаться на значение x после ручного переключения потоков на await. 

Собсно, знания о том, какие значения являются детерминпированными, а какие нет, тоже составляют фундамент многопоточного программирования. ))
В последнем примере x1 - это личный ресурс, а x - разделяемый.
Но это не "само так получилось", не? Это так код был специально написан, чтобы x стал разделяемым ресурсом, да еще чтобы обязательно что-то с ним произошло м/у операциями чтения или записи. В последнем примере достаточно поменять строку (1) со строчкой сверху или снизу, чтобы ты опять вернулся к детерминированным рассуждениям относительно x.


I>Если компилятор не гарантирует атомарность инкремента

Компилятор ничего не может гарантировать. Он просто использует низлежащую модель вычислений.

I>многозадачный код ломает этот инвариант.
I>Отсюда ясно, что не состояние неопределено, а порядок выполнения недетерминирован, ибо может быть реально так:

I>thread1 read i
I>thread2 read i
I>thread1 write i + 1
I>thread2 write i + 1

Слава те хосподя, об этом и пример ))

I>И именно эта последовательность ломает состояние. То есть, гонки налицо. Видишь их? 

Для случая С++? Конечно, это же был мой пример.


I>Теперь твой пример на JS, вместо потока - задача. Что читаем i и записываем отдельно.
I>Запускаем один таск - никаких нарушений инварианта нет, всё шоколадно.
I>Запускаем второй таск, опаньки - инвариант ломается.

Ну он же не сам поломался, это ты специально, ручками, сделал так, чтобы переменные i и actual изменялись/проверялись в РАЗНЫХ циклах запуска жабаскрипта. А ты сделай так, чтобы эти переменные изменялись в рамках одного цикла запуска скрипта и всё будет ОК. 

I>Здесь, внимание, порядок выполнения зависит исключительно от того, в какой момент стартовал новый таск. 

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

Ты вообще смотрел мой пример? Не обратил внимание на такой момент:
[java]
// подпишись сюда в браузере на событие с клавиатуры
function onKeyboard(nextChar)
{
  keyboardQueue.push(nextChar);

  if(keyboardAwaiter != null) {
    var awaiter = keyboardAwaiter;
    keyboardAwaiter = null;
    awaiter(keyboardQueue.take());
  }
}

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

Но зато к самой очереди я обращаюсь БЕЗ каких-либо блокировок, т.к. низлежащая модель вычислений такова, что весь мой код, считай, обложен парой DisableInterruptions/EnableInterruptions.

И ты, именно ты, как программист, должен ЗНАТЬ низлежащую вычислительную модель. Ты должен знать, что твой жабасрипт НЕ прервется ни в какой точке исходника до тех пор, пока ты сам же, явно его не прервёшь. И если ты не обладаешь этими знаниями, то ты еще не готов программировать на этом зыке. Вот и всё кино.

I>ОС следит только за протоколом файловой системы. Что ты запишешь в файл, ты не знаешь, если писателей больше одного и твоей синхронизации нет.

I>И если два потока пишут в один файл, не важно, логических или физических, результат может быть недерминирован.

Ну так открывай файл с исключительным доступом, елки. И не шарь владение хендлом на файл.
Я пока проблему никак не могу уловить.
Не мог бы ты озвучить ТЗ целиком?


I>Это, в частности, проявляется в том, что содержимое файла при одном запуске будет "111222" а во втором случае "121212" а в третьем "122211"


Ну это согласно ТЗ так или из-за ошибок в коде?
Re[18]: Почему Эрланг
Здравствуйте, Ikemefula, Вы писали:

I>Кстати, вопрос такой — ты в курсе, что Windows до определенной версии поддерживала только кооперетивную многозадачность ?

I>Ты в курсе, что уже тогда в ней были и семафоры, и муткесы.

Семафоры и мьютексы более использовались для сигналинга м/у процессами, внутри одного процесса обычно пользовали CRITICAL_SECTION.

Ну и еще в Windows 16 работали перывания прямо в юзверском коде ))
Поэтому, если твоя прога сидела на прерываниях, то эти прерывания тоже как-то обыгрывались: либо ресурсы защищались теми же критическими секциями, либо для какого-то участка кода запрещались прерывания.


I>Как так, ведь в кооперативной многозадачности "гонок нет"


В Windows 16 (если ты о ней) кооперативный шедулер срабатывал на КАЖДЫЙ вызов системного АПИ, наверно в этом дело?

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

I>Все очень просто — ты хочешь съехать с кооперативной многозадачности.


Ы?

I>У тебя вытесняющая многозадачность, а я говорю про кооперативную. Представь себе, гонки это свойство любой многозадачности, а не только вытесняющей.


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

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

В базе жабаскрипта есть вполне детерминированная однопоточная модель вычислений — ты можешь пользоваться знаниями об этом.

Например, в случае вытесняющей многозадачности, для сериализации доступа к разделяемому ресурсу ты должен писать как-то так:
try {
   Monitor.enter(guard1);
   resource.access1();  // (1)
   resource.access2();  // (2)
} finally {
   Monitor.Exit(guard1);
}

Что логически эквивалентно сишному:
try {
   RaiiGuard g(criticalSection1);
   resource.access1();  // (1)
   resource.access2();  // (2)
}


Но в жабаскрипте для обеспечения детерминированности достаточно, чтобы м/у точками (1) и (2) не произошло переключения задач. Т.е. не вставляй м/у точками (1) и (2) никаких await/yield и всех делов.

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


I>У тебя плохой пример. Раздели чтение переменной и запись на две операции. Внезапно, обнаруживаем инвариант — поток затирает ровно то значение, которое перед этим прочёл.


Да, но где здесь недетерминированность?
Вот у меня код на жабаскрипте:
var x = 1;
x = x * 10;
x = x - 5;

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

Теперь другой пример:
var x = 1;

async function f1() {
  var x1 = x * 10;
  await someAsyncOp(); // (1)
  x = x1 - 5;
}


Пример тоже полностью детерминированный. Ты можешь полагаться на значение локальной переменной x1, но уже не можешь полагаться на значение x после ручного переключения потоков на await.

Собсно, знания о том, какие значения являются детерминпированными, а какие нет, тоже составляют фундамент многопоточного программирования. ))
В последнем примере x1 — это личный ресурс, а x — разделяемый.
Но это не "само так получилось", не? Это так код был специально написан, чтобы x стал разделяемым ресурсом, да еще чтобы обязательно что-то с ним произошло м/у операциями чтения или записи. В последнем примере достаточно поменять строку (1) со строчкой сверху или снизу, чтобы ты опять вернулся к детерминированным рассуждениям относительно x.


I>Если компилятор не гарантирует атомарность инкремента


Компилятор ничего не может гарантировать. Он просто использует низлежащую модель вычислений.

I>многозадачный код ломает этот инвариант.

I>Отсюда ясно, что не состояние неопределено, а порядок выполнения недетерминирован, ибо может быть реально так:

I>thread1 read i

I>thread2 read i
I>thread1 write i + 1
I>thread2 write i + 1

Слава те хосподя, об этом и пример ))

I>И именно эта последовательность ломает состояние. То есть, гонки налицо. Видишь их?


Для случая С++? Конечно, это же был мой пример.


I>Теперь твой пример на JS, вместо потока — задача. Что читаем i и записываем отдельно.

I>Запускаем один таск — никаких нарушений инварианта нет, всё шоколадно.
I>Запускаем второй таск, опаньки — инвариант ломается.

Ну он же не сам поломался, это ты специально, ручками, сделал так, чтобы переменные i и actual изменялись/проверялись в РАЗНЫХ циклах запуска жабаскрипта. А ты сделай так, чтобы эти переменные изменялись в рамках одного цикла запуска скрипта и всё будет ОК.

I>Здесь, внимание, порядок выполнения зависит исключительно от того, в какой момент стартовал новый таск.


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

Ты вообще смотрел мой пример? Не обратил внимание на такой момент:
// подпишись сюда в браузере на событие с клавиатуры
function onKeyboard(nextChar)
{
  keyboardQueue.push(nextChar);

  if(keyboardAwaiter != null) {
    var awaiter = keyboardAwaiter;
    keyboardAwaiter = null;
    awaiter(keyboardQueue.take());
  }
}

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

Но зато к самой очереди я обращаюсь БЕЗ каких-либо блокировок, т.к. низлежащая модель вычислений такова, что весь мой код, считай, обложен парой DisableInterruptions/EnableInterruptions.

И ты, именно ты, как программист, должен ЗНАТЬ низлежащую вычислительную модель. Ты должен знать, что твой жабасрипт НЕ прервется ни в какой точке исходника до тех пор, пока ты сам же, явно его не прервёшь. И если ты не обладаешь этими знаниями, то ты еще не готов программировать на этом зыке. Вот и всё кино.

I>ОС следит только за протоколом файловой системы. Что ты запишешь в файл, ты не знаешь, если писателей больше одного и твоей синхронизации нет.

I>И если два потока пишут в один файл, не важно, логических или физических, результат может быть недерминирован.

Ну так открывай файл с исключительным доступом, елки. И не шарь владение хендлом на файл.
Я пока проблему никак не могу уловить.
Не мог бы ты озвучить ТЗ целиком?


I>Это, в частности, проявляется в том, что содержимое файла при одном запуске будет "111222" а во втором случае "121212" а в третьем "122211"


Ну это согласно ТЗ так или из-за ошибок в коде?