Семафор Javascript / тест-и-набор / блокировка?
Есть ли в Javascript такая вещь, как атомарный тест и набор, семафор или блокировка?
У меня есть JavaScript, вызывающий асинхронные фоновые процессы через пользовательский протокол (фоновый процесс буквально выполняется в отдельном процессе, не связанном с браузером). Я полагаю, что я нахожусь в состоянии гонки; фоновый процесс возвращается между моим тестом и моим сетом, что портит все на стороне javascript. Мне нужна операция test-and-set, чтобы сделать ее настоящим семафором.
Вот код javascript, который пытается обнаружить фоновые процессы и поставить их в очередь:
Call = function () {
var isRunning = true,
queue = [];
return {
// myPublicProperty: "something",
call: function (method) {
if (isRunning) {
console.log("Busy, pushing " + method);
queue.push(method);
} else {
isRunning = true;
objccall(method);
}
},
done: function() {
isRunning = false;
if (queue.length > 0) {
Call.call(queue.shift());
}
}
};
}();
Call - это синглтон, который реализует очередь; любой, кто хочет вызвать внешний процесс, делает Call.call("что-то") .
Есть идеи?
7 ответов
У JavaScript нет семантики блокировки, потому что JS не является многопоточным языком. Несколько потоков могут работать одновременно только в совершенно разных контекстах, например. Рабочие потоки HTML5 или такие вещи, как множественные экземпляры объекта контекста API JavaScriptCore (я предполагаю, что SpiderMonkey имеет похожую концепцию). Они не могут иметь общего состояния, поэтому, по сути, все исполнение является атомарным.
Хорошо, так как вы сейчас предоставили часть своего кода, я предполагаю, что у вас есть что-то похожее на:
External Process:
<JSObject>.isRunning = true;
doSomething()
<JSObject>.done()
Или некоторые такие (с использованием соответствующих API). В этом случае я ожидал бы, что механизм JS будет блокироваться, если JS выполняется в контексте вашего объекта js (что будет делать JavaScriptCore), в противном случае вам, вероятно, потребуется установить ручную блокировку вокруг выполнения js.
Какой движок вы используете для всего этого? Я спрашиваю, потому что, исходя из вашего описания, звучит так, как будто вы устанавливаете флаг из вторичного потока из языка, отличного от JS, с помощью API C/C++, предоставляемого этим языком, и большинство движков JS предполагают, что любая манипуляция состоянием осуществляется через API будет происходить в одном потоке, обычно в том же потоке, в котором выполняется все выполнение.
Прежде всего, хотя это правда, что javaScript является однопоточным, это НЕ правда, что приложение javaScript никогда не требует механизма сериализации.
Простой пример, когда кнопка отправки должна исчезать в течение заданного промежутка времени, в течение которого работает Ajax-запрос к серверу. Когда асинхронный Ajax-запрос успешно завершается, должно появиться сообщение о том, где раньше находилась кнопка.
Хотя было бы неплохо иметь возможность отменить затухание кнопки и просто установить ее стиль на "display: none", как только запрос Ajax завершится, это невозможно в jQuery. Кроме того, решение может использовать События для синхронизации двух одновременных действий, но это по существу излишне для простой проблемы.
Низкотехнологичное решение заключается в том, чтобы опросить блокировку, и, когда затухание завершается, она разблокируется, но сообщение "сервер завершен" НЕ отображается до тех пор, пока не будет выполнен обратный вызов успеха, заданный $.post.
var gl_lock;
var gl_selfID;
function poll_lock(message) {
if (gl_lock === 0) {
$('#output').text(message).fadeIn(200);
window.clearInterval(gl_selfID);
}
} // end of poll_lock
function worker() {
// no one gets in or out
gl_lock = 1;
$.post(..., data,function() {
gl_selfID = window.setInterval(poll_lock, 40, data.message);
}, "json");
// end of fadeout unlock the semaphore
$('#submit-button').fadeOut(400, function() { gl_lock = 0; });
} // end of worker
Наконец, я думаю, что это более подробный ответ в соответствии с тем, что ранее предлагал Perrohunter в этой дискуссии.
Может быть, вы могли бы реализовать базовый целочисленный семафор, просто добавьте переменную в DOM и заблокируйте / разблокируйте ее и убедитесь, что ваши функции продолжают проверять ее, в противном случае время ожидания их =)
Если вы используете фреймворк, такой как Mootools, вы можете попытаться обработать поток приложения с помощью таких событий, как onComplete и так далее.
Я не совсем уверен, что именно задает вопрос, но посмотрите мой объект семафора здесь: https://github.com/agamemnus/semaphore.js.
У меня была такая же проблема, вот как я ее решил. Он отлично работает для двух одновременных процессов. Если у вас есть три или более процессов, возможно, что два процесса запускаются вместе.
var isRunning = false;
...
var id = setInterval(function(){ //loop until isRunning becomes false
if (!isRunning){
isRunning = true;
//do something synchronous or use a promise
isRunning = false;
clearInterval(id); // stop the loop here
}
, 10);
Это лучше, чем цикл while, потому что он решает проблемы параллелизма / асинхронности для чтения / настройки isRunning
.
У меня есть Ajax, который заполняет списки выбора, мне нужно было его заблокировать, поэтому я сделал что-то вроде этого. Я думаю, что вы, вероятно, могли бы сделать это проще, используя отложенные и конвейерные каналы или что-то еще
var semaphore=[];
function myFunc(arg){
var dfd;
$.when(semaphore[table]).done(
function(){
dfd=myFuncInner(arg);
}
);
return dfd;
}
function myFuncInner(table){
semaphore[arg] = new $.Deferred();
...
somethingasynchronous({
semaphore[arg].resolve();
});
return semaphore[arg];
}
Я использовал
async-mutex
пакет для решения проблемы, из-за которой Expo SQLite не блокирует BEGIN TRANSACTION в iOS . Вы создаете мьютекс и оборачиваете критические разделы в
runExclusive
метод
return this.mutex.runExclusive(async () => {
try {
await this.executeSqlAsync('begin immediate transaction');
const tx = new AsyncSQLTransaction(this);
const rs = await callback(tx);
await this.executeSqlAsync('commit');
return rs;
} catch (error) {
await this.executeSqlAsync('rollback');
throw error;
}
});