<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>hudson@work &#187; урок</title>
	<atom:link href="http://hudson.su/tag/lesson/feed/" rel="self" type="application/rss+xml" />
	<link>http://hudson.su</link>
	<description>статьи о web-разработке, менеджменте IT проектов и контроле качества</description>
	<lastBuildDate>Fri, 20 Jan 2012 13:15:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	
		<item>
		<title>Асинхронный пул на jQuery</title>
		<link>http://hudson.su/2010/02/17/asynchronous-pool-on-jquery/</link>
		<comments>http://hudson.su/2010/02/17/asynchronous-pool-on-jquery/#comments</comments>
		<pubDate>Wed, 17 Feb 2010 08:30:41 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[web разработка]]></category>
		<category><![CDATA[продвинутый]]></category>
		<category><![CDATA[урок]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=943</guid>
		<description><![CDATA[Приведенный ниже скрипт имеет скорее теоретический нежели практический интерес. Итак, постановка задачи: Есть скрипт получения некоего набора данных (JSON). Требуется на основе этого набора данных циклически вызывать асинхронный запрос, который выполняет действия на основе входных данных от первого скрипта. При этом требуется лимитировать число одновременно запущенных асинхронных процессов (чтобы не порождать десятки или даже сотни [...]]]></description>
			<content:encoded><![CDATA[<p>Приведенный ниже скрипт имеет скорее теоретический нежели практический интерес.</p>
<h3>Итак, постановка задачи:</h3>
<ul>
<li>Есть скрипт получения некоего набора данных (JSON).</li>
<li>Требуется на основе этого набора данных циклически вызывать асинхронный запрос, который выполняет действия на основе входных данных от первого скрипта.</li>
<li>При этом требуется лимитировать число одновременно запущенных асинхронных процессов (чтобы не порождать десятки или даже сотни запросов сразу).</li>
</ul>
<h3>Алгоритм решения:</h3>
<ul>
<li>Получить JSON с данными для последующей обработки.</li>
<li>Для каждого элемента в полученных данных:</li>
<li>&#8211; Если пул не заполнен &#8211; запустить асинхронный процесс.</li>
<li>&#8211; Если пул заполнен &#8211; ждать пока в пуле освободится слот.</li>
<li>По окончанию обработки данных очистить пул.</li>
</ul>
<h3>Проблема:</h3>
<ul>
<li>Если организовывать опрос пула циклически, съедается 100% одного ядра CPU, начинает дико тормозить интернет-обозреватель и в конце концов может аварийно завершить работу.</li>
</ul>
<p>Как можно реализовать скрипт для данной задачи &#8211; смотрите ниже:</p>
<p><span id="more-943"></span><br />
Начнем с приминания травы &#8211; заготовим каркас страницы:</p>
<pre>&lt;html&gt;
&lt;head&gt;
 &lt;title&gt;asynchronous pool on jquery demo&lt;/title&gt;
 &lt;script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js" type="text/javascript"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;script&gt;
// тут будет наш скрипт
&lt;/script&gt;
&lt;div&gt;
 &lt;div id="stat"&gt;тут статистика&lt;/div&gt;
 &lt;div id="ajax-results"&gt;тут результаты&lt;/div&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>Небольшая теоретическая вводная по эмуляции многопоточности в javascript: <a href="http://javascript.ru/blog/tenshi/mnogopotochnyi-yavaskript" target="_blank">http://javascript.ru/blog/tenshi/mnogopotochnyi-yavaskript</a>. Что дает эта статья? Способ избежать 100%-й загрузки CPU. Применяем:</p>
<pre>&lt;script&gt;
/**
 * 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;
}</pre>
<p>Sleep позволяет добиться некоего подобия многопоточности, так как &#8220;форкает&#8221; таймер с callback-функцией, где переопределяется self.handler. Также нам понадобится менеджер пула:</p>
<pre>/**
 * Pool manager
 */
 $.poolManager = function( input, id, data ){
   switch( input.message ){
     case 'start':
       if( this.state !== 'wait' ) return this( input, id, data );
       this.state = 'run';
       this.iteration = 0;
     case 'next':
       if( this.state !== 'run' ) return;
       this.iteration++;
       if( !$.pushInPool( id, data ) ) return this( { message: 'next' }, id, data ); // no slot in pool
       return this( { message: 'finish' } );
     case 'finish':
       if( this.state !== 'run' ) return;
       this.state= 'wait';
       return;
     return;
   }
}.process({ state: 'wait' })</pre>
<p>Пока этот код нам ничего не дает. Добавим стартер процесса:</p>
<pre>/**
 * Main entry point
 */
 $.main = function(){
   $.results_container = $( "#ajax-results" );
   $.results_container.html( "" );
   $.results_container.append( $( "&lt;div&gt;" ).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 = [];
 }</pre>
<p>В данной функции получаем данные и запускаем цикли их обработки. Изначально вместо for(&#8230;) $.poolManager(&#8230;) была конструкция вида</p>
<ul>
<li>для каждого чанка</li>
<li>пока не добавлен в пул запусти таймер на N милисекунд</li>
</ul>
<p>Логичная вроде бы конструкция. Только вот она и съедала 100% CPU, приводила к подвисанию и даже крэшу браузера. Давайте теперь взглянем на вставку задачи в пул:</p>
<pre>/**
 * Push active work in pool
 */
 $.pushInPool = function( id, data ){
   if( $.sizeof( $.threads_pool ) &lt; this.pool_settings["pool_size"] ){
     $.threads_pool.push( id );
     $.workChunk( id, data );
     return true;
   }
   return false;
 }</pre>
<p>Тут все просто &#8211; если размер пула равен критическому, возвращаем &#8220;ложь&#8221;, если нет, то добавляем в массив значение с ID треда и запускаем воркер для текущего чанка. Воркер выглядит так:</p>
<pre>/**
 * Do one chunk of work
 */
 $.workChunk = function( id, data ){
   $.results_container
     .append( $( "&lt;div&gt;" )
     .html(
       "Iteration. Working with " + $.sizeof( data ) + " items"
       + "&lt;div id="+id+" style='display:inline;'&gt;&lt;img src='indicator.gif'&gt;"
   ));
   $.ajax({
     type: "POST",
     url: "doAction.php",
     data: {},
     success: function( data, textStatus, XMLHttpRequest ){
       $.success_threads++;
       $( "#"+id ).html( " - &lt;b style='color:green;'&gt;OK&lt;/b&gt;" );
     },
     complete: function( XMLHttpRequest, textStatus ){
       $.removeFromPool( id );
       $("#stat").html("").append("Success: " + $.success_threads + "&lt;br&gt;Failed: " + $.failed_threads + "&lt;br&gt;Success percentage: " + (($.success_threads)/($.success_threads+$.failed_threads))*100 );
     },
     error: function( XMLHttpRequest, textStatus, errorThrown ){
       $.failed_threads++;
       $( "#"+id ).html( " - &lt;b style='color:red;'&gt;FAILED&lt;/b&gt;" );
     }
 });
 }</pre>
<p>Самый главный тут &#8211; ajax запрос, он же, вне зависимости от того как отработает, удаляет процесс из пула:</p>
<pre>/**
 * Remove finished work from pool
 */
 $.removeFromPool = function( id ){
   for( i in $.threads_pool ){
     if( $.threads_pool[i] == id ){
       $.threads_pool.splice( i, 1 );
       return true;
     }
   }
   return false;
 }</pre>
<p>Для удаления делаем проход по пулу и ищем ID текущего чанка и если находим, вырезаем при помощи splice.</p>
<p>Вот в общем-то и все.</p>
<p><a href="http://hudson.su/wp-content/demo/2010/02/async-pool-demo/" target="_blank">Можете попробовать демо</a>.</p>
<p>Или <a href="http://hudson.su/?attachment_id=945" target="_blank">скачать пример себе</a>:</p>
<ul>
<li>распакуйте его в любой директории вашего веб-сервера, где выполнится PHP скрипт и запустите asynchronous-pool-on-jquery.php.</li>
<li>В комплект входят также:</li>
<li>getData.php &#8211; скрипт эмулирует отдачу JSON данных</li>
<li>doAction.php &#8211; воркер, который имеет случайную задержку (чтобы локально все отрабатывало не так быстро) и с вероятностью около 50% он возвращает error 500 &#8211; для того чтобы протестировать исключение чанка из пула при ошибке.</li>
</ul>
<p>Вот в общем-то и все. Надеюсь было интересно )</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/02/17/asynchronous-pool-on-jquery/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

