Параллелизм Ajax

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

function tick() {
    // This function is called once every second
    time -= 1;
    redisplay(time);
};
function update(newtime) {
    // This function is called whenever the ajax request
    // to the server yields a new time
    time = newtime;
};

Это, конечно, немного сложнее, но вы можете увидеть присущее состояние гонки. Что, если и функция обновления, и функция тика пытаются изменить time одновременно?

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

Спасибо.


person So8res    schedule 16.01.2010    source источник
comment
Всегда хотел задать этот вопрос.   -  person zedoo    schedule 16.01.2010
comment
Просто для ясности, этот код работает в браузере или на сервере работает в чем-то вроде node.js?   -  person Kevin Hakanson    schedule 16.01.2010
comment
Это работает в браузере.   -  person So8res    schedule 16.01.2010


Ответы (5)


У вас нет состояния гонки, потому что Javascript не выполняется одновременно.

Каждый раз, когда обратный вызов запускается из асинхронной операции (AJAX, setTimeout и т. д.), этот обратный вызов должен завершить выполнение, прежде чем можно будет вызвать другой обратный вызов из другой асинхронной операции. Так что, если update() работает, никакой другой Javascript не работает. После завершения update() могут быть запущены другие обратные вызовы из асинхронных операций (например, tick()). Интересно, что именно поэтому setTimeout и ему подобные не гарантируют выполнение точно в момент достижения тайм-аута: какой-то другой javascript может блокировать выполнение обратного вызова.

Посетите http://ejohn.org/blog/how-javascript-timers-work/ для получения полезной информации о том, как все это работает.

person ShZ    schedule 16.01.2010

Javascript является однопоточным. Нет условий гонки. В javascript две строки time = newtime; и time -= 1; не могут перекрываться во время выполнения. На самом деле обе функции гарантированно не пересекаются. Один из них побежит, а затем побежит другой.

person PanCrit    schedule 16.01.2010

Я был неправ: это не решает проблему! (пояснение после кода)

NEWTIME = false;
function tick() {
    // This function is called once every second
    if (NEWTIME) {
        time = NEWTIME;
        NEWTIME = false;
    }
    time -= 1;
    redisplay(time);
};
function update(newtime) {
    // This function is called whenever the ajax request
    // to the server yields a new time
    NEWTIME = newtime;
};

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

Только подумайте: выполнение tick достигает и выполняет строку time = NEWTIME; сейчас, прежде чем продолжить, вызывается обновление, и NEWTIME получает значение X. Теперь выполнение тика продолжает выполнение NEWTIME = false;. Таким образом, вы потеряли X значение NEWTIME и, следовательно, эффект вызова update()!

person Andrea Zilio    schedule 16.01.2010
comment
Это именно то, что я намеревался сделать, как это не решает проблему гонки? Вы можете ошибиться на цикл, но мне не кажется, что NEWTIME потеряется. - person Justin Johnson; 16.01.2010
comment
@Justin Таким образом, вы просто перемещаете проблему состояния гонки из переменной time в переменную NEWTIME. Только подумайте: выполнение тика достигает и выполняет строку time = NEWTIME; сейчас, прежде чем продолжить, вызывается update, а NEWTIME получает значение X. Теперь выполнение tick продолжает выполнение NEWTIME = false;. Таким образом, вы потеряли эффект вызова update()! - person Andrea Zilio; 16.01.2010
comment
Да, но вы теряете его максимум на один тик, в отличие от исходного способа, который приводит к его потере навсегда, если язык был параллельным. - person Justin Johnson; 18.01.2010
comment
@Justin: Нет, предположим, вы находитесь в точке исполнения, где time равно 100, а NEWTIME равно 150; теперь tick выполняет строку time = NEWTIME;, поэтому теперь time равно 150. Теперь, прежде чем продолжить выполнение tick, update вызывается с первым аргументом newtime, установленным на 200. В update выполняется строка NEWTIME = newtime, поэтому NEWTIME теперь равно 200. Теперь выполнение tick возобновляется и NEWTIME = false выполняется, поэтому эффект только что выполненного вызова update теряется, и вы потеряли 50 секунд на своем таймере! :) Очевидно, что это уже не так, поскольку javascript является однопоточным :) - person Andrea Zilio; 18.01.2010
comment
Вы сказали, что вы теряете его только на максимум один тик, в отличие от исходного способа, который приводит к его потере навсегда, если язык был параллельным . Это неправильно, потому что, принимая во внимание пример, объясненный в моем предыдущем комментарии, если язык был параллельным, вы теряете 50 секунд своего таймера навсегда. Таким образом, решение, о котором мы говорим, не касается состояния гонки. Именно поэтому ответ - Нет. - person Andrea Zilio; 18.01.2010

Задача нуждается в некоторых семафорах. Я часто делаю это для отправки ajax после завершения предыдущего. Ваш случай немного похож ;)

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

window.TimeKeeper = new function(){
this.time=0; //initial value, whatever
this.isopen=true;

this.decrement = function(){ 
 if(this.isopen){
  this.time--;
  redisplay(this.time);
  }
 }
this.set = function(val){
 if(this.isopen){
  this.isopen=false;
  this.time=val;
  this.isopen=true;
  } else {
  //another AJAX callback is modifying time. 
   //You can enqueue current value and put it later
  }
 }

}

function tick() {
    TimeKeeper.decrement();

};
function update(newtime) {
    TimeKeeper.set(newtime);
};

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

И один небольшой совет — избегайте слишком частых запросов с помощью AJAX — это может вызвать дополнительные проблемы — например, запросы возвращаются в другом порядке, чем отправленные, и использование памяти Firefox сильно увеличивается при слишком большом количестве ajax в минуту;)

person naugtur    schedule 16.01.2010
comment
Спасибо. Что произойдет, если декремент не удастся? Время не отображается повторно. В этом случае вы можете просто переместить вызов повторного отображения за пределы if, но для более общего случая существует ли неблокирующий способ подождать и повторить попытку через мгновение? Кроме того, мне нужно довольно часто проверять наличие обновлений с сервера, что на данный момент означает, что я делаю один вызов ajax в секунду. Если мне нужны обновления так часто, есть ли лучший способ сделать это, чем ajax? У сервера нет возможности отправить уведомление клиентам, когда происходит событие, не так ли? - person So8res; 16.01.2010
comment
Сейчас у меня нет времени глубоко проверять, но это не кажется мне хорошим решением... - person Andrea Zilio; 16.01.2010
comment
Javascript является однопоточным. Вам не нужны семафоры. Худшее, что может случиться, это то, что происходит уменьшение (за которым следует повторное отображение), а затем значение немедленно перезаписывается вновь предоставленным значением из обновления. В этом случае время будет уменьшаться и отображаться на секунду позже. Не проблема. - person PanCrit; 16.01.2010
comment
Этот ответ очень неверен :(. setTimeout совсем не работает как новый поток, и вам не нужен семафор или какой-либо примитив синхронизации вообще для обеспечения атомарности. - person ShZ; 16.01.2010

Пока вы добавляете еще несколько секунд к текущему времени, все должно быть в порядке.

Вместо time = newtime; попробуйте time += newtime; Так вы не потеряете ни секунды, о которой беспокоитесь.

Тем не менее, вы теряете только секунду в худшем случае.

person Horia Dragomir    schedule 16.01.2010
comment
Обратите внимание, новое время — это то, сколько еще времени нужно добавить. - person Horia Dragomir; 16.01.2010
comment
Я не думаю, что это решает проблему состояния гонки - person Andrea Zilio; 16.01.2010