Приведенный ниже скрипт имеет скорее теоретический нежели практический интерес.
Итак, постановка задачи:
Есть скрипт получения некоего набора данных (JSON).
Требуется на основе этого набора данных циклически вызывать асинхронный запрос, который выполняет действия на основе входных данных от первого скрипта.
При этом требуется лимитировать число одновременно запущенных асинхронных процессов (чтобы не порождать десятки или даже сотни запросов сразу).
Алгоритм решения:
Получить JSON с данными для последующей обработки.
Для каждого элемента в полученных данных:
– Если пул не заполнен – запустить асинхронный процесс.
– Если пул заполнен – ждать пока в пуле освободится слот.
По окончанию обработки данных очистить пул.
Проблема:
Если организовывать опрос пула циклически, съедается 100% одного ядра CPU, начинает дико тормозить интернет-обозреватель и в конце концов может аварийно завершить работу.
Как можно реализовать скрипт для данной задачи – смотрите ниже:
Начнем с приминания травы – заготовим каркас страницы:
<html><head><title>asynchronous pool on jquery demo</title><scriptsrc="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"type="text/javascript"></script></head><body><script>
// тут будет наш скрипт
</script><div><divid="stat">тут статистика</div><divid="ajax-results">тут результаты</div></div></body></html>
<script>/**
* Emulate mulithreading
*/Function.prototype.process=function( state ){var process =function(){var args = arguments;var self = arguments.callee;
setTimeout(function(){
self.handler.apply( self, args );},500)}for(var i in state ) process[ i ]= state[ i ];
process.handler=this;return process;}
Sleep позволяет добиться некоего подобия многопоточности, так как “форкает” таймер с callback-функцией, где переопределяется self.handler. Также нам понадобится менеджер пула:
/**
* Pool manager
*/
$.poolManager=function( input, id, data ){switch( input.message){case'start':if(this.state!=='wait')returnthis( input, id, data );this.state='run';this.iteration=0;case'next':if(this.state!=='run')return;this.iteration++;if(!$.pushInPool( id, data ))returnthis({ message:'next'}, id, data );// no slot in poolreturnthis({ message:'finish'});case'finish':if(this.state!=='run')return;this.state='wait';return;return;}}.process({ state:'wait'})
Пока этот код нам ничего не дает. Добавим стартер процесса:
/**
* Main entry point
*/
$.main=function(){
$.results_container= $("#ajax-results");
$.results_container.html("");
$.results_container.append( $("<div>").html("Chunk size: "+ $.pool_settings["chunk_size"]+"; Pool size: "+ $.pool_settings["pool_size"]));// get data
$.getJSON("getData.php",{chunk_size: $.pool_settings["chunk_size"]},function( data ){for( i in data ){
$.poolManager({message:"start"},"i-"+i, data[i]);}});// flush pool when finished
$.threads_pool=[];}
В данной функции получаем данные и запускаем цикли их обработки. Изначально вместо for(…) $.poolManager(…) была конструкция вида
для каждого чанка
пока не добавлен в пул запусти таймер на N милисекунд
Логичная вроде бы конструкция. Только вот она и съедала 100% CPU, приводила к подвисанию и даже крэшу браузера. Давайте теперь взглянем на вставку задачи в пул:
/**
* Push active work in pool
*/
$.pushInPool=function( id, data ){if( $.sizeof( $.threads_pool)<this.pool_settings["pool_size"]){
$.threads_pool.push( id );
$.workChunk( id, data );returntrue;}returnfalse;}
Тут все просто – если размер пула равен критическому, возвращаем “ложь”, если нет, то добавляем в массив значение с ID треда и запускаем воркер для текущего чанка. Воркер выглядит так:
/**
* Do one chunk of work
*/
$.workChunk=function( id, data ){
$.results_container
.append( $("<div>")
.html("Iteration. Working with "+ $.sizeof( data )+" items"+"<div id="+id+" style='display:inline;'><img src='indicator.gif'>"));
$.ajax({
type:"POST",
url:"doAction.php",
data:{},
success:function( data, textStatus, XMLHttpRequest ){
$.success_threads++;
$("#"+id ).html(" - <b style='color:green;'>OK</b>");},
complete:function( XMLHttpRequest, textStatus ){
$.removeFromPool( id );
$("#stat").html("").append("Success: "+ $.success_threads+"<br>Failed: "+ $.failed_threads+"<br>Success percentage: "+(($.success_threads)/($.success_threads+$.failed_threads))*100);},
error:function( XMLHttpRequest, textStatus, errorThrown ){
$.failed_threads++;
$("#"+id ).html(" - <b style='color:red;'>FAILED</b>");}});}
Самый главный тут – ajax запрос, он же, вне зависимости от того как отработает, удаляет процесс из пула:
/**
* Remove finished work from pool
*/
$.removeFromPool=function( id ){for( i in $.threads_pool){if( $.threads_pool[i]== id ){
$.threads_pool.splice( i,1);returntrue;}}returnfalse;}
Для удаления делаем проход по пулу и ищем ID текущего чанка и если находим, вырезаем при помощи splice.
распакуйте его в любой директории вашего веб-сервера, где выполнится PHP скрипт и запустите asynchronous-pool-on-jquery.php.
В комплект входят также:
getData.php – скрипт эмулирует отдачу JSON данных
doAction.php – воркер, который имеет случайную задержку (чтобы локально все отрабатывало не так быстро) и с вероятностью около 50% он возвращает error 500 – для того чтобы протестировать исключение чанка из пула при ошибке.
Асинхронный пул на jQuery
Приведенный ниже скрипт имеет скорее теоретический нежели практический интерес.
Итак, постановка задачи:
Алгоритм решения:
Проблема:
Как можно реализовать скрипт для данной задачи – смотрите ниже:
Начнем с приминания травы – заготовим каркас страницы:
Небольшая теоретическая вводная по эмуляции многопоточности в javascript: http://javascript.ru/blog/tenshi/mnogopotochnyi-yavaskript. Что дает эта статья? Способ избежать 100%-й загрузки CPU. Применяем:
Sleep позволяет добиться некоего подобия многопоточности, так как “форкает” таймер с callback-функцией, где переопределяется self.handler. Также нам понадобится менеджер пула:
Пока этот код нам ничего не дает. Добавим стартер процесса:
В данной функции получаем данные и запускаем цикли их обработки. Изначально вместо for(…) $.poolManager(…) была конструкция вида
Логичная вроде бы конструкция. Только вот она и съедала 100% CPU, приводила к подвисанию и даже крэшу браузера. Давайте теперь взглянем на вставку задачи в пул:
Тут все просто – если размер пула равен критическому, возвращаем “ложь”, если нет, то добавляем в массив значение с ID треда и запускаем воркер для текущего чанка. Воркер выглядит так:
Самый главный тут – ajax запрос, он же, вне зависимости от того как отработает, удаляет процесс из пула:
Для удаления делаем проход по пулу и ищем ID текущего чанка и если находим, вырезаем при помощи splice.
Вот в общем-то и все.
Можете попробовать демо.
Или скачать пример себе:
Вот в общем-то и все. Надеюсь было интересно )