APPOSSUM - ТЕХ. ПОДРОБНОСТИ

Все аппы

↓ Громкость: 80 % ↑

Громкость: 80 %

ГЕНЕРАТОР РРРИТМОВ ПОСТУЧАТЬ Кто не умеет играть на этнических барабанах — узнает многое о ритмах, даже сможет их сочинять. Кто умеет — получит инструмент, чтобы делиться барабанными знаниями. Но больше всего Генератор ррритмов Drrrums подойдёт тем, кто учится и экспериментирует. МАНДАЛА НЕЛЬСОНА НАРИСОВАТЬ Просто двигайте мышью (можно под музыку). Если действовать намеренно — это будет рисованием в необычном графическом редакторе. Если вы просто позволите визуальным образам возникать и исчезать, это может стать медитацией. ДИКТАНТЫ КАТАЛОГ (558 ЗАДАНИЙ) Апп диктует слова — вы пишете. Если пишете грамотно, то цель достигнута. В некоторых заданиях надо сразу переводить слова на другой язык. Сейчас работают три словаря: русский, английский, немецкий . E.MEMORY КАТАЛОГ (281 ЗАДАНИЙ) Апп-игра для запоминания любых соответствий, в первую очередь иностранных слов. Например, стол — это table , про Монте-Кристо написал Дюма , Сеул — столица Южной Кореи . Много заданий из школьных и институтских учебников. ЧИСЛА КАТАЛОГ (14 ЗАДАНИЙ) Генератор математических примеров пригодится тем, кто учит своих или чужих детей арифметике. Примеры можно распечатать или решать онлайн, тогда апп сам всё проверит. YOBEL ХОЧУ ПРАЗДНИК Вычисление необычных юбилеев. Узнайте, когда вам стукнет миллиард секунд или сто тысяч лет в двоичной системе — и отпразднуйте! Или найдите повод поздравить кого-то другого.

ГЕНЕРАТОР РРРИТМОВ ПОСТУЧАТЬ Кто не умеет играть на этнических барабанах — узнает многое о ритмах, даже сможет их сочинять. Кто умеет — получит инструмент, чтобы делиться барабанными знаниями. Но больше всего Генератор ррритмов Drrrums подойдёт тем, кто учится и экспериментирует.

Кто не умеет играть на этнических барабанах — узнает многое о ритмах, даже сможет их сочинять. Кто умеет — получит инструмент, чтобы делиться барабанными знаниями. Но больше всего Генератор ррритмов Drrrums подойдёт тем, кто учится и экспериментирует.

МАНДАЛА НЕЛЬСОНА НАРИСОВАТЬ Просто двигайте мышью (можно под музыку). Если действовать намеренно — это будет рисованием в необычном графическом редакторе. Если вы просто позволите визуальным образам возникать и исчезать, это может стать медитацией.

Просто двигайте мышью (можно под музыку). Если действовать намеренно — это будет рисованием в необычном графическом редакторе. Если вы просто позволите визуальным образам возникать и исчезать, это может стать медитацией.

ДИКТАНТЫ КАТАЛОГ (558 ЗАДАНИЙ) Апп диктует слова — вы пишете. Если пишете грамотно, то цель достигнута. В некоторых заданиях надо сразу переводить слова на другой язык. Сейчас работают три словаря: русский, английский, немецкий .

Апп диктует слова — вы пишете. Если пишете грамотно, то цель достигнута. В некоторых заданиях надо сразу переводить слова на другой язык. Сейчас работают три словаря: русский, английский, немецкий .

E.MEMORY КАТАЛОГ (281 ЗАДАНИЙ) Апп-игра для запоминания любых соответствий, в первую очередь иностранных слов. Например, стол — это table , про Монте-Кристо написал Дюма , Сеул — столица Южной Кореи . Много заданий из школьных и институтских учебников.

Апп-игра для запоминания любых соответствий, в первую очередь иностранных слов. Например, стол — это table , про Монте-Кристо написал Дюма , Сеул — столица Южной Кореи . Много заданий из школьных и институтских учебников.

ЧИСЛА КАТАЛОГ (14 ЗАДАНИЙ) Генератор математических примеров пригодится тем, кто учит своих или чужих детей арифметике. Примеры можно распечатать или решать онлайн, тогда апп сам всё проверит.

Генератор математических примеров пригодится тем, кто учит своих или чужих детей арифметике. Примеры можно распечатать или решать онлайн, тогда апп сам всё проверит.

YOBEL ХОЧУ ПРАЗДНИК Вычисление необычных юбилеев. Узнайте, когда вам стукнет миллиард секунд или сто тысяч лет в двоичной системе — и отпразднуйте! Или найдите повод поздравить кого-то другого.

Вычисление необычных юбилеев. Узнайте, когда вам стукнет миллиард секунд или сто тысяч лет в двоичной системе — и отпразднуйте! Или найдите повод поздравить кого-то другого.

ТЕХ. ПОДРОБНОСТИ ПРАВИЛА И ИНСТРУКЦИИ КОНТАКТЫ ПОСЛЕДНИЕ ИЗМЕНЕНИЯ ТЕХ. ПОДРОБНОСТИ Вернуться к списку статей JavaScript и потоки. Что есть и что будет. Автор: А. Смирнов Текст опубликован: 22.02.2010 Твитнуть Работая с кодом Javascript (в части его исполнения в контексте HTML-страниц, загруженных в браузер) надо четко представлять себе, что, по крайней мере в настоящее время, реализации языка Javascript не являются многопотоковыми по всему массиву наиболее популярных браузеров. Парадоксально, но событийная модель реализаций этого языка является асинхронной , но не является многопотоковой . Как будет показано в дальнейшем на примерах, для исполнения всего кода Javascript браузером создается один поток, в котором последовательно, по очереди, исполняются блоки кода. В ту же очередь встают и обработчики событий. Этот вопрос довольно скромно освещается в литературе: во времена легендарного браузера Netscape Navigator его создатели честно заявляли именно такую модель обработки скриптов, однако относили это не к стандартному поведению скриптов вообще, а к особенностям реализации своего браузера, обещая в будущем это поведение пересмотреть. Сейчас же найти такую информацию по современным браузерам довольно затруднительно. Это подвигло в свое время популярного технического блоггера Дэна Симарда ( Dan Simard, http://www.javascriptkata.com/ ) разработать простой тест для исследования поведения Javascript’а в контексте различных браузеров. Соответствующая запись в его блоге озаглавлена “Ajax, javascript and threads : the final truth” ( http://www.javascriptkata.com/2007/06/12/ajax-javascript-and-threads-the-final-truth/ ). Вкратце (а заметка именно такова) сделанный им вывод таков: допустим, что в функции F(), исполняющейся 5 секунд, первым оператором идет AJAX-запрос, исполняющийся 2 секунды. Так вот, обработчик ответа будет запущен не через 2 секунды, а через 5, после завершения исполнения предыдущего блока кода - функции F(). В рамках этой статьи мы творчески переработаем его эксперимент, пройдем ряд тестов и опишем получившиеся результаты. Начнем с реализации функции-хронометра, работающей заданный временной интервал (10 секунд). В отсутствие спецсредств в самом языке (что как раз и обуславливается его однопоточностью, так как в других языках программирования функции типа sleep и pause обычно входят в состав модулей работы с потоками) применим следующий вариант: threads_test0.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; } div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > И в Internet Explorer 7, и в Firefox 3.5 будет получен одинаковый результат: start: Mon Aug 24 2009 12:27:35 GMT+0400 finish: Mon Aug 24 2009 12:27:45 GMT+0400 При этом те 10 секунд, за которые отработает функция, кнопка Жми будет находиться в нажатом состоянии. То есть, как минимум, код функции и код, обслуживающий UI, исполняется в одном потоке, последовательно, блок за блоком. Примечание: категорически не рекомендуется использовать вышеприведенную функцию в качестве функции паузы. Как мы видим, жизнь браузера на время ее исполнения полностью замирает. Более того, нервные экземпляры браузеров подозревают ее в зацикливании, и, рано или поздно, реагируют соответствующим системным сообщением. Расширим наш пример, добавив в него запуск по таймауту (асинхронно) функции fn2 (почему 2 станет ясно чуть позже). Время запуска настроим на 20 секунд, через 10 секунд после окончания работы хронометра: threads_test1.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var fn2 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn2: ' + ( new Date ( ) ) + '<br>' ; } , 20000 ) ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; } div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > Будет получен следующий результат (причем кнопка Жми будет перерисована через 10 секунд): start : Mon Aug 24 2009 12:39:14 GMT+0400 finish: Mon Aug 24 2009 12:39:24 GMT+0400 fn2: Mon Aug 24 2009 12:39:34 GMT+0400 Из этого следует 2 вывода: время отработки хронометра не влияет на расписание настроенных на запуск по таймауту на более поздний срок функций; запуск функции по таймауту позволяет браузеру провести ревизию ожидающих исполнения действий и отработать их. Добавим в пример запуск по таймауту (асинхронно) функции fn1 . Время запуска настроим на 5 секунд, за 5 секунд до окончания работы хронометра: threads_test2.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var fn1 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn1: ' + ( new Date ( ) ) + '<br>' ; } , 5000 ) ; var fn2 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn2: ' + ( new Date ( ) ) + '<br>' ; } , 20000 ) ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; } div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > Результат тестов соответствует той самой окончательной правде Симарда: start: Mon Aug 24 2009 13:11:44 GMT+0400 finish: Mon Aug 24 2009 13:11:54 GMT+0400 fn1: Mon Aug 24 2009 13:11:54 GMT+0400 fn2: Mon Aug 24 2009 13:12:04 GMT+0400 То есть функция fn1 , которая должна была бы (при наличии многопоточности) сработать через 5 секунд, ставится в очередь на запуск и отрабатывает сразу после завершения работы предыдущего блока кода (хронометра). На всякий случай проверим конспирологическую версию о том, что исполнение функции fn1 задерживает работа с элементами UI. Перепишем функцию, локализовав работу с UI после обращения к системным часам: var fn1 = setTimeout ( function ( ) { var dt = ( new Date ( ) ) ; var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn1: ' + dt + '<br>' ; } , 5000 ) ; Результирующая страница не изменится, версия не принимается. Проверим еще одну версию – не учитывает ли браузер при исполнении кода Javascript деление кода на функции (не является ли именно функция неделимым функциональным блоком). Для этого оформим текст хронометра в виде функций: threads_test3.html < html > < head > < script > function inner1 ( ) { Math. random ( ) ; } function inner2 ( ) { var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { inner1 ( ) ; } } function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var fn1 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn1: ' + ( new Date ( ) ) + '<br>' ; } , 5000 ) ; var fn2 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn2: ' + ( new Date ( ) ) + '<br>' ; } , 20000 ) ; inner2 ( ) ; div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > Результирующая страница не изменится, версия не принимается. Усложним тест, добавив на страницу вторую кнопку. Нажав первую кнопку (она, как мы помним, остается нажатой до окончания работы хронометра), щелкнем сразу же и по второй (ее вид при этом не изменится): threads_test4.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var fn1 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn1: ' + ( new Date ( ) ) + '<br>' ; } , 5000 ) ; var fn2 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn2: ' + ( new Date ( ) ) + '<br>' ; } , 20000 ) ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; } div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } function timer2 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'еще: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < button onclick = "timer2()" > жми еще < / button > < div id = "holder" >< / div > < / body > < / html > Результирующая страница выглядит так (в Firefox): start: Mon Aug 24 2009 14:57:03 GMT+0400 finish: Mon Aug 24 2009 14:57:13 GMT+0400 fn1: Mon Aug 24 2009 14:57:13 GMT+0400 еще: Mon Aug 24 2009 14:57:13 GMT+0400 fn2: Mon Aug 24 2009 14:57:23 GMT+0400 …или так (в Internet Explorer): start: Mon Aug 24 14:59:01 UTC+0400 2009 finish: Mon Aug 24 14:59:11 UTC+0400 2009 еще: Mon Aug 24 14:59:11 UTC+0400 2009 fn1: Mon Aug 24 14:59:11 UTC+0400 2009 fn2: Mon Aug 24 14:59:21 UTC+0400 2009 Таким образом, мы видим, что браузер не “забыл” про вторую нажатую кнопку, запомнил этот факт, поместил код обработчика нажатия в очередь блоков кода на обработку и исполнил при первой возможности (при этом порядок исполнения 2х блоков отложенного таким образом кода – от таймаута и от нажатия на кнопку, зависит от конкретного браузера). Добавим в функцию, запускаемую по таймауту, настройку еще одной функции ( fn3 ), также запускаемой по таймауту. threads_test5.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var fn1 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn1: ' + ( new Date ( ) ) + '<br>' ; var fn3 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn3: ' + ( new Date ( ) ) + '<br>' ; } , 5000 ) ; } , 5000 ) ; var fn2 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn2: ' + ( new Date ( ) ) + '<br>' ; } , 20000 ) ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; } div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > Полученный результат говорит о том, что отложенная функция fn1 живет по реальному системному времени в момент ее исполнения (и от него отсчитывает время запуска fn3), а не по тому времени, в который она должна была бы исполниться, не работай в это время хронометр. start: Mon Aug 24 2009 15:06:58 GMT+0400 finish: Mon Aug 24 2009 15:07:08 GMT+0400 fn1: Mon Aug 24 2009 15:07:09 GMT+0400 fn3: Mon Aug 24 2009 15:07:14 GMT+0400 fn2: Mon Aug 24 2009 15:07:18 GMT+0400 Еще больше запутаем ситуацию, введя второй хронометр в функцию fn3: threads_test6.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var fn1 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn1: ' + ( new Date ( ) ) + '<br>' ; var fn3 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start fn3: ' + ( new Date ( ) ) + '<br>' ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; } div. innerHTML = div. innerHTML + 'finish fn3: ' + ( new Date ( ) ) + '<br>' ; } , 5000 ) ; } , 5000 ) ; var fn2 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn2: ' + ( new Date ( ) ) + '<br>' ; } , 20000 ) ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; } div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > … и убедимся в том, что его исполнение отодвинет во времени давно запланированный запуск по таймауту функции fn2 (T(fn2)-T(start)>20 секунд) start: Mon Aug 24 2009 15:12:00 GMT+0400 finish: Mon Aug 24 2009 15:12:10 GMT+0400 fn1: Mon Aug 24 2009 15:12:10 GMT+0400 start fn3: Mon Aug 24 2009 15:12:15 GMT+0400 finish fn3: Mon Aug 24 2009 15:12:26 GMT+0400 fn2: Mon Aug 24 2009 15:12:26 GMT+0400 Очень интересна последовательность отрисовки: первые 3 строки выводятся в15:12:10 (тогда же перерисовывается и кнопка Жми), следующие 3 – в 15:12:26. То есть отработка любого из хронометров задерживает отрисовку (как частный случай исполнения функционального блока), которая отрабатывает после их окончания. Таким образом, для браузера страшен не длинный (по времени исполнения) код в принципе, а длинный код, исполняемый одним функциональным блоком, без использования функций по таймауту (при этом размер таймаутов не так важен). Или: чтобы дать браузеру возможность во время выполнения длинного кода решать и другие задачи (например отрисовывать промежуточные результаты) разбейте код на блоки (каждый такой блок должен исполняться быстро), запускаемые по таймауту (поставив размер таймаута в 1, чтобы не терять на нем время). Несколько видоизменяют работу браузера по формированию и обработке очередей блоков кода функции, отображающие модальные диалоги (alert, confirm etc.). Рассмотрим следующий код: threads_test7.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var fn1 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn1: ' + ( new Date ( ) ) + '<br>' ; } , 10000 ) ; alert ( "pause" ) ; div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > Первое, на что следует обратить внимание: диалог был отрисован сразу же, не дожидаясь окончания функции-хронометра. То есть при появлении в тексте модального диалога браузер отрабатывает его в экстренном порядке. А заодно и другие события отрисовки в очереди: мы видим, что сразу же была перерисована кнопка и выведено время старта (1я строка). Оставим теперь без внимания кнопку OK диалога секунд на 30. Результат будет выглядеть следующим образом: start: Mon Aug 24 2009 17:51:45 GMT+0400 finish: Mon Aug 24 2009 17:52:16 GMT+0400 fn1: Mon Aug 24 2009 17:52:16 GMT+0400 То есть после отрисовки диалога жизнь браузера замирает до ответа пользователя – никакие другие скрипты в это время не отрабатываются. Проверим теперь, не отработает ли браузер кроме кода отрисовки еще и функции, застрявшие в очереди исполнения на момент показа диалога. Исполним следующий код (к моменту вывода сообщения в очереди уже ждет функция fn1): threads_test8.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var fn1 = setTimeout ( function ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'fn1: ' + ( new Date ( ) ) + '<br>' ; } , 5000 ) ; var t1 = new Date ( ) . getTime ( ) ; var was_executed = false ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 15000 ) { Math. random ( ) ; if ( ! was_executed && ( new Date ( ) . getTime ( ) ) - t1 > 8000 ) { alert ( "pause" ) ; was_executed = true ; } } div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > Не будем долго мурыжить диалог и щелкнем по ОК сразу. Получим следующий текст на странице: start: Mon Aug 24 2009 18:04:56 GMT+0400 finish: Mon Aug 24 2009 18:05:11 GMT+0400 fn1: Mon Aug 24 2009 18:05:11 GMT+0400 То есть пропихнуть вместе с отрисовкой исполнение функции fn1 нам не удалось. Интересно, что браузеру Internet Explorer удается перед возвращением в цикл хронометра стереть окно диалога (что логично с точки зрения пользователя), а браузер Firefox оставляет его висеть до окончания работы хронометра. Перейдем от функций, запускаемых по таймауту, к отработке асинхронных событий AJAX. Подключим библиотеку jQuery и выполним с ее помощью простой запрос к порталу Flickr по технологии JSONP (это позволит нам тестировать асинхронную модель событий AJAX на локальных страницах, обойдя проблему получения данных из других доменов). Запросим набор ссылок по некоей интересующей нас теме (опоссумы) и выведем время его отработки: threads_test9.html < html > < head > < script type = "text/javascript" src = "jquery-1.3.2.js" >< / script > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; $. getJSON ( "http://api.flickr.com/services/feeds/photos_public.gne" + "?tags=opossum&tagmode=any&format=json&jsoncallback=?" , function ( data ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'JSONP: ' + ( new Date ( ) ) + '<br>' ; } ) ; div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > Как мы видим, запрос исполняется практически мгновенно (время отклика порядка секунды): start: Tue Aug 25 2009 12:37:53 GMT+0400 finish: Tue Aug 25 2009 12:37:53 GMT+0400 JSONP: Tue Aug 25 2009 12:37:54 GMT+0400 Теперь займем браузер делом – подставим в код стандартную функцию хронометра из предыдущих тестов: threads_test10.html < html > < head > < script type = "text/javascript" src = "jquery-1.3.2.js" >< / script > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; $. getJSON ( "http://api.flickr.com/services/feeds/photos_public.gne" + "?tags=opossum&tagmode=any&format=json&jsoncallback=?" , function ( data ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'JSONP: ' + ( new Date ( ) ) + '<br>' ; } ) ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; } div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > Полученный результат четко демонстрирует нам уже изученное на примере функций отложенного таймаутом запуска поведение. Разбор пришедшего ответа был направлен в очередь блоков кода на исполнение и выполнен после освобождения единственного потока исполнения кодов функцией-хронометром. start: Tue Aug 25 2009 12:42:08 GMT+0400 finish: Tue Aug 25 2009 12:42:18 GMT+0400 JSONP: Tue Aug 25 2009 12:42:19 GMT+0400 Ожидаемые инновации, способные изменить описанную картину (перемены уже здесь – все примеры рабочие) В браузере Firefox 3.5 реализована концепция т.н. Worker Threads (независимых рабочих потоков), позволяющая выполнять параллельные вычисления в Javascript, не требующие (существенно важное ограничение) непосредственного взаимодействия с пользовательским интерфейсом (UI). Worker Threads – стандартная концепция, реализованная в соответствии со спецификациями Web Hypertext Application Technology Working Group ( WHATWG ), работающей совместно с W3C HTML working group над спецификациями HTML 5 и прочими инновациями в области стандартизации технологий веб-приложений. В силу того, что рассматриваемая реализация опирается на готовящиеся стандарты, ее реализации в аналогичном ключе уже доступны в браузере Safari 4, платформе Google Gears etc., то есть можно ожидать, что в будущем они станут неотъемлемой частью ландшафта WWW. Worker – определенным образом оформленный код, загружаемый и исполняемый в отдельном потоке: new Worker("worker.js") ; Внимание: на действия, выполняемые Worker’ом, накладываются жесткие ограничения: Worker не имеет доступа к объектам DOM Worker не имеет прямого доступа к родительской таблице. Для того, чтобы задать Worker'у параметры работы, а также отобразить результаты его деятельности, требуется механизм обмена информацией между его потоком и основным потоком исполнения кода в браузере (не имеющем этих ограничений). В качестве подобного коммуникативного механизма используется протокол обмена сообщениями postMessage API (входящий в спецификацию HTML 5). При этом как в коде, исполняющемся в основном потоке, так и в коде Worker’а должны быть явно прописаны обработчики входящих сообщений. Рассмотрим пример. Worker ждет запроса от основного потока, при его получении исполняет функцию и сообщает основному потоку о проделанной работе. Основной поток инициализирует Worker’а, шлет ему запрос на исполнение функции и выводит результат его работы. worker0.js onmessage = function ( e ) { if ( e. data === "start" ) { doIt } } ; function doIt { postMessage ( "done" ) ; } worker0.html < html > < head > < script > function timer1 ( ) { var worker = new Worker ( "worker0.js" ) ; worker. onmessage = function ( e ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + e. data ; } ; worker. postMessage ( "start" ) ; } ; < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > Результат таков: done Рассмотрим одно из очевидных применений описанной технологии – вынесение в отдельный поток (потоки) длительных вычислений. Рассмотрим пример страницы, производящей большие вычислительные работы, без применения спецтехники: threads_test12.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { // здесь могут быть важные вычисления Math. random ( ) ; } div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } ; function doClick ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'clicked: ' + ( new Date ( ) ) + '<br>' ; } ; < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < button onclick = "doClick()" > нажми-ка < / button > < div id = "holder" >< / div > < / body > < / html > Запустим тест, нажмем на кнопку Жми и попытаемся несколько (3) раза нажать на кнопку Нажми-ка. Во-первых, все время выполнения вычислений UI не будет перерисовываться и не отразит наши нажатия. Во-вторых, все дополнительные нажатия отработают только после завершения вычислений: start: Tue Aug 25 2009 15:51:05 GMT+0400 finish: Tue Aug 25 2009 15:51:15 GMT+0400 clicked: Tue Aug 25 2009 15:51:16 GMT+0400 clicked: Tue Aug 25 2009 15:51:16 GMT+0400 clicked: Tue Aug 25 2009 15:51:16 GMT+0400 А теперь вынесем вычисления в отдельный поток, вернув их результаты посылкой сообщения основному потоку: worker3.js var response = 'worker start: ' + ( new Date ( ) ) + '<br>' ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { // здесь могли бы быть вычисления Math. random ( ) ; } response += 'worker finish: ' + ( new Date ( ) ) + '<br>' ; postMessage ( response ) ; worker3.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var worker = new Worker ( "worker3.js" ) ; worker. onmessage = function ( e ) { div. innerHTML = div. innerHTML + 'message received: ' + ( new Date ( ) ) + '<br>' ; div. innerHTML = div. innerHTML + e. data ; } ; div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } ; function doClick ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'clicked: ' + ( new Date ( ) ) + '<br>' ; } ; < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < button onclick = "doClick()" > нажми-ка < / button > < div id = "holder" >< / div > < / body > < / html > Полный успех! Результат соответствует самым оптимистическим ожиданиям: start: Tue Aug 25 2009 15:58:49 GMT+0400 finish: Tue Aug 25 2009 15:58:49 GMT+0400 clicked: Tue Aug 25 2009 15:58:51 GMT+0400 clicked: Tue Aug 25 2009 15:58:54 GMT+0400 clicked: Tue Aug 25 2009 15:58:58 GMT+0400 message received: Tue Aug 25 2009 15:58:59 GMT+0400 worker start: Tue Aug 25 2009 15:58:49 GMT+0400 worker finish: Tue Aug 25 2009 15:58:59 GMT+0400 clicked: Tue Aug 25 2009 15:59:01 GMT+0400 То есть: вычисления проведены точно в заказанное время; их результаты отображены; UI доступен все время работы; события основного потока отрабатывают без задержек! Попробуем использовать полученные знания в нашем классическом примере с функцией-хронометром. Запустим 3 независимых потока вычислений – в основном потоке и двух дополнительных: worker1.js onmessage = function ( e ) { if ( e. data . substr ( 0 , 5 ) === "start" ) { doIt ( e. data . substr ( 5 , 1 ) ) ; } } ; function doIt ( num ) { var cnt = 0 ; var response = 'worker ' + num + 'start: ' + ( new Date ( ) ) + '<br>' ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 5000 ) { Math. random ( ) ; cnt ++; } response += 'worker ' + num + ' result: ' + cnt + '<br>' ; response += 'worker' + num + ' finish: ' + ( new Date ( ) ) + '<br>' ; postMessage ( response ) ; } worker1.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var worker1 = new Worker ( "worker1.js" ) ; worker1. onmessage = function ( e ) { div. innerHTML = div. innerHTML + 'message 1 received: ' + ( new Date ( ) ) + '<br>' ; div. innerHTML = div. innerHTML + e. data ; } ; var worker2 = new Worker ( "worker1.js" ) ; worker2. onmessage = function ( e ) { div. innerHTML = div. innerHTML + 'message 2 received: ' + ( new Date ( ) ) + '<br>' ; div. innerHTML = div. innerHTML + e. data ; } ; worker1. postMessage ( "start1" ) ; worker2. postMessage ( "start2" ) ; var cnt = 0 ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; cnt ++; } div. innerHTML = div. innerHTML + 'result: ' + cnt + '<br>' ; div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } ; < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > К несчастью, здесь нас ждет неудача. Да, вычисления в дополнительных потоках выполнялись параллельно, но после завершения вычислений в основном потоке: start: Tue Aug 25 2009 16:44:46 GMT+0400 result: 5275005 finish: Tue Aug 25 2009 16:44:56 GMT+0400 message 2 received: Tue Aug 25 2009 16:45:03 GMT+0400 worker 2 start: Tue Aug 25 2009 16:44:56 GMT+0400 worker 2 result: 2462832 worker 2 finish: Tue Aug 25 2009 16:45:03 GMT+0400 message 1 received: Tue Aug 25 2009 16:45:03 GMT+0400 worker 1 start: Tue Aug 25 2009 16:44:56 GMT+0400 worker 1 result: 2483128 worker 1 finish: Tue Aug 25 2009 16:45:03 GMT+0400 По моей версии запуск Worker thread’ов для Javascript является асинхронным событием и стандартным образом помещается в очередь блоков кода на исполнение, где и ждет окончания работы функции-хронометра. Версия о том, что асинхронно работает функция postMessage тоже проверялась (был написан код с непосредственным запуском потоков), но не прошла. Следующий вариант финального комплексного примера демонстрирует как раз требуемое поведение. В нем мы оформляем хронометр в асинхронно исполняемый блок кода, помещаемый в очередь на исполнение после кода запуска потоков, и все наши 3 потока вычислений (основной и 2 дополнительных) выполняются параллельно. Однако, поскольку вывод результатов на страницу (операции с DOM) возможен только в основном потоке, а он занят более длительными вычислениями, результаты из дополнительных потоков мы увидим только после завершения функции-хронометра: Worker2.html < html > < head > < script > function timer1 ( ) { var div = document. getElementById ( 'holder' ) ; div. innerHTML = div. innerHTML + 'start: ' + ( new Date ( ) ) + '<br>' ; var worker1 = new Worker ( "worker1.js" ) ; worker1. onmessage = function ( e ) { div. innerHTML = div. innerHTML + 'message 1 received: ' + ( new Date ( ) ) + '<br>' ; div. innerHTML = div. innerHTML + e. data ; } ; var worker2 = new Worker ( "worker1.js" ) ; worker2. onmessage = function ( e ) { div. innerHTML = div. innerHTML + 'message 2 received: ' + ( new Date ( ) ) + '<br>' ; div. innerHTML = div. innerHTML + e. data ; } ; worker1. postMessage ( "start1" ) ; worker2. postMessage ( "start2" ) ; setTimeout ( function ( ) { var cnt = 0 ; var t1 = new Date ( ) . getTime ( ) ; while ( ( new Date ( ) . getTime ( ) ) - t1 < 10000 ) { Math. random ( ) ; cnt ++; } div. innerHTML = div. innerHTML + 'result: ' + cnt + '<br>' ; div. innerHTML = div. innerHTML + 'finish: ' + ( new Date ( ) ) + '<br>' ; } , 1 ) ; } ; < / script > < / head > < body > < button onclick = "timer1()" > жми < / button > < div id = "holder" >< / div > < / body > < / html > start: Tue Aug 25 2009 17:01:53 GMT+0400 result: 4265458 finish: Tue Aug 25 2009 17:02:03 GMT+0400 message 2 received: Tue Aug 25 2009 17:02:03 GMT+0400 worker 2 start: Tue Aug 25 2009 17:01:53 GMT+0400 worker 2 result: 1896424 worker 2 finish: Tue Aug 25 2009 17:01:58 GMT+0400 message 1 received: Tue Aug 25 2009 17:02:03 GMT+0400 worker 1 start: Tue Aug 25 2009 17:01:53 GMT+0400 worker 1 result: 516569 worker 1 finish: Tue Aug 25 2009 17:01:58 GMT+0400 В заключение отметим, что код Worker thread’ов имеет доступ к объекту XMLHttpRequest, а потому возможны варианты с параллельной динамической подгрузкой и обработкой информации. Но с последовательным ее выводом через основной поток.

ТЕХ. ПОДРОБНОСТИ

ПРАВИЛА И ИНСТРУКЦИИ

КОНТАКТЫ

ПОСЛЕДНИЕ ИЗМЕНЕНИЯ

Работая с кодом Javascript (в части его исполнения в контексте HTML-страниц, загруженных в браузер) надо четко представлять себе, что, по крайней мере в настоящее время, реализации языка Javascript не являются многопотоковыми по всему массиву наиболее популярных браузеров. Парадоксально, но событийная модель реализаций этого языка является асинхронной , но не является многопотоковой .

Как будет показано в дальнейшем на примерах, для исполнения всего кода Javascript браузером создается один поток, в котором последовательно, по очереди, исполняются блоки кода. В ту же очередь встают и обработчики событий.

Этот вопрос довольно скромно освещается в литературе: во времена легендарного браузера Netscape Navigator его создатели честно заявляли именно такую модель обработки скриптов, однако относили это не к стандартному поведению скриптов вообще, а к особенностям реализации своего браузера, обещая в будущем это поведение пересмотреть. Сейчас же найти такую информацию по современным браузерам довольно затруднительно. Это подвигло в свое время популярного технического блоггера Дэна Симарда ( Dan Simard, http://www.javascriptkata.com/ ) разработать простой тест для исследования поведения Javascript’а в контексте различных браузеров. Соответствующая запись в его блоге озаглавлена “Ajax, javascript and threads : the final truth” ( http://www.javascriptkata.com/2007/06/12/ajax-javascript-and-threads-the-final-truth/ ).

Вкратце (а заметка именно такова) сделанный им вывод таков: допустим, что в функции F(), исполняющейся 5 секунд, первым оператором идет AJAX-запрос, исполняющийся 2 секунды. Так вот, обработчик ответа будет запущен не через 2 секунды, а через 5, после завершения исполнения предыдущего блока кода - функции F().

В рамках этой статьи мы творчески переработаем его эксперимент, пройдем ряд тестов и опишем получившиеся результаты. Начнем с реализации функции-хронометра, работающей заданный временной интервал (10 секунд). В отсутствие спецсредств в самом языке (что как раз и обуславливается его однопоточностью, так как в других языках программирования функции типа sleep и pause обычно входят в состав модулей работы с потоками) применим следующий вариант:

threads_test0.html

И в Internet Explorer 7, и в Firefox 3.5 будет получен одинаковый результат:

При этом те 10 секунд, за которые отработает функция, кнопка Жми будет находиться в нажатом состоянии. То есть, как минимум, код функции и код, обслуживающий UI, исполняется в одном потоке, последовательно, блок за блоком.

Примечание: категорически не рекомендуется использовать вышеприведенную функцию в качестве функции паузы. Как мы видим, жизнь браузера на время ее исполнения полностью замирает. Более того, нервные экземпляры браузеров подозревают ее в зацикливании, и, рано или поздно, реагируют соответствующим системным сообщением.

Расширим наш пример, добавив в него запуск по таймауту (асинхронно) функции fn2 (почему 2 станет ясно чуть позже). Время запуска настроим на 20 секунд, через 10 секунд после окончания работы хронометра:

threads_test1.html

Будет получен следующий результат (причем кнопка Жми будет перерисована через 10 секунд):

Из этого следует 2 вывода:

время отработки хронометра не влияет на расписание настроенных на запуск по таймауту на более поздний срок функций;

запуск функции по таймауту позволяет браузеру провести ревизию ожидающих исполнения действий и отработать их.

Добавим в пример запуск по таймауту (асинхронно) функции fn1 . Время запуска настроим на 5 секунд, за 5 секунд до окончания работы хронометра:

threads_test2.html

Результат тестов соответствует той самой окончательной правде Симарда:

То есть функция fn1 , которая должна была бы (при наличии многопоточности) сработать через 5 секунд, ставится в очередь на запуск и отрабатывает сразу после завершения работы предыдущего блока кода (хронометра).

На всякий случай проверим конспирологическую версию о том, что исполнение функции fn1 задерживает работа с элементами UI. Перепишем функцию, локализовав работу с UI после обращения к системным часам:

Результирующая страница не изменится, версия не принимается.

Проверим еще одну версию – не учитывает ли браузер при исполнении кода Javascript деление кода на функции (не является ли именно функция неделимым функциональным блоком). Для этого оформим текст хронометра в виде функций:

threads_test3.html

Усложним тест, добавив на страницу вторую кнопку. Нажав первую кнопку (она, как мы помним, остается нажатой до окончания работы хронометра), щелкнем сразу же и по второй (ее вид при этом не изменится):

threads_test4.html

Результирующая страница выглядит так (в Firefox):

…или так (в Internet Explorer):

Таким образом, мы видим, что браузер не “забыл” про вторую нажатую кнопку, запомнил этот факт, поместил код обработчика нажатия в очередь блоков кода на обработку и исполнил при первой возможности (при этом порядок исполнения 2х блоков отложенного таким образом кода – от таймаута и от нажатия на кнопку, зависит от конкретного браузера).

Добавим в функцию, запускаемую по таймауту, настройку еще одной функции ( fn3 ), также запускаемой по таймауту.

threads_test5.html

Полученный результат говорит о том, что отложенная функция fn1 живет по реальному системному времени в момент ее исполнения (и от него отсчитывает время запуска fn3), а не по тому времени, в который она должна была бы исполниться, не работай в это время хронометр.

Еще больше запутаем ситуацию, введя второй хронометр в функцию fn3:

threads_test6.html

… и убедимся в том, что его исполнение отодвинет во времени давно запланированный запуск по таймауту функции fn2 (T(fn2)-T(start)>20 секунд)

Очень интересна последовательность отрисовки: первые 3 строки выводятся в15:12:10 (тогда же перерисовывается и кнопка Жми), следующие 3 – в 15:12:26. То есть отработка любого из хронометров задерживает отрисовку (как частный случай исполнения функционального блока), которая отрабатывает после их окончания.

Таким образом, для браузера страшен не длинный (по времени исполнения) код в принципе, а длинный код, исполняемый одним функциональным блоком, без использования функций по таймауту (при этом размер таймаутов не так важен). Или: чтобы дать браузеру возможность во время выполнения длинного кода решать и другие задачи (например отрисовывать промежуточные результаты) разбейте код на блоки (каждый такой блок должен исполняться быстро), запускаемые по таймауту (поставив размер таймаута в 1, чтобы не терять на нем время).

Несколько видоизменяют работу браузера по формированию и обработке очередей блоков кода функции, отображающие модальные диалоги (alert, confirm etc.). Рассмотрим следующий код:

threads_test7.html

Первое, на что следует обратить внимание: диалог был отрисован сразу же, не дожидаясь окончания функции-хронометра. То есть при появлении в тексте модального диалога браузер отрабатывает его в экстренном порядке. А заодно и другие события отрисовки в очереди: мы видим, что сразу же была перерисована кнопка и выведено время старта (1я строка). Оставим теперь без внимания кнопку OK диалога секунд на 30. Результат будет выглядеть следующим образом:

То есть после отрисовки диалога жизнь браузера замирает до ответа пользователя – никакие другие скрипты в это время не отрабатываются.

Проверим теперь, не отработает ли браузер кроме кода отрисовки еще и функции, застрявшие в очереди исполнения на момент показа диалога. Исполним следующий код (к моменту вывода сообщения в очереди уже ждет функция fn1):

threads_test8.html

Не будем долго мурыжить диалог и щелкнем по ОК сразу. Получим следующий текст на странице:

То есть пропихнуть вместе с отрисовкой исполнение функции fn1 нам не удалось. Интересно, что браузеру Internet Explorer удается перед возвращением в цикл хронометра стереть окно диалога (что логично с точки зрения пользователя), а браузер Firefox оставляет его висеть до окончания работы хронометра.

Перейдем от функций, запускаемых по таймауту, к отработке асинхронных событий AJAX. Подключим библиотеку jQuery и выполним с ее помощью простой запрос к порталу Flickr по технологии JSONP (это позволит нам тестировать асинхронную модель событий AJAX на локальных страницах, обойдя проблему получения данных из других доменов). Запросим набор ссылок по некоей интересующей нас теме (опоссумы) и выведем время его отработки:

threads_test9.html

Как мы видим, запрос исполняется практически мгновенно (время отклика порядка секунды):

Теперь займем браузер делом – подставим в код стандартную функцию хронометра из предыдущих тестов:

threads_test10.html

Полученный результат четко демонстрирует нам уже изученное на примере функций отложенного таймаутом запуска поведение. Разбор пришедшего ответа был направлен в очередь блоков кода на исполнение и выполнен после освобождения единственного потока исполнения кодов функцией-хронометром.

(перемены уже здесь – все примеры рабочие)

В браузере Firefox 3.5 реализована концепция т.н. Worker Threads (независимых рабочих потоков), позволяющая выполнять параллельные вычисления в Javascript, не требующие (существенно важное ограничение) непосредственного взаимодействия с пользовательским интерфейсом (UI). Worker Threads – стандартная концепция, реализованная в соответствии со спецификациями Web Hypertext Application Technology Working Group ( WHATWG ), работающей совместно с W3C HTML working group над спецификациями HTML 5 и прочими инновациями в области стандартизации технологий веб-приложений.

В силу того, что рассматриваемая реализация опирается на готовящиеся стандарты, ее реализации в аналогичном ключе уже доступны в браузере Safari 4, платформе Google Gears etc., то есть можно ожидать, что в будущем они станут неотъемлемой частью ландшафта WWW.

Worker – определенным образом оформленный код, загружаемый и исполняемый в отдельном потоке:

new Worker("worker.js") ;

Внимание: на действия, выполняемые Worker’ом, накладываются жесткие ограничения:

Worker не имеет доступа к объектам DOM

Worker не имеет прямого доступа к родительской таблице.

Для того, чтобы задать Worker'у параметры работы, а также отобразить результаты его деятельности, требуется механизм обмена информацией между его потоком и основным потоком исполнения кода в браузере (не имеющем этих ограничений). В качестве подобного коммуникативного механизма используется протокол обмена сообщениями postMessage API (входящий в спецификацию HTML 5). При этом как в коде, исполняющемся в основном потоке, так и в коде Worker’а должны быть явно прописаны обработчики входящих сообщений.

Рассмотрим пример. Worker ждет запроса от основного потока, при его получении исполняет функцию и сообщает основному потоку о проделанной работе. Основной поток инициализирует Worker’а, шлет ему запрос на исполнение функции и выводит результат его работы.

worker0.js

worker0.html

Результат таков:

Рассмотрим одно из очевидных применений описанной технологии – вынесение в отдельный поток (потоки) длительных вычислений. Рассмотрим пример страницы, производящей большие вычислительные работы, без применения спецтехники:

threads_test12.html

Запустим тест, нажмем на кнопку Жми и попытаемся несколько (3) раза нажать на кнопку Нажми-ка. Во-первых, все время выполнения вычислений UI не будет перерисовываться и не отразит наши нажатия. Во-вторых, все дополнительные нажатия отработают только после завершения вычислений:

А теперь вынесем вычисления в отдельный поток, вернув их результаты посылкой сообщения основному потоку:

worker3.js

worker3.html

Полный успех! Результат соответствует самым оптимистическим ожиданиям:

То есть:

вычисления проведены точно в заказанное время;

их результаты отображены;

UI доступен все время работы;

события основного потока отрабатывают без задержек!

Попробуем использовать полученные знания в нашем классическом примере с функцией-хронометром. Запустим 3 независимых потока вычислений – в основном потоке и двух дополнительных:

worker1.js

worker1.html

К несчастью, здесь нас ждет неудача. Да, вычисления в дополнительных потоках выполнялись параллельно, но после завершения вычислений в основном потоке:

По моей версии запуск Worker thread’ов для Javascript является асинхронным событием и стандартным образом помещается в очередь блоков кода на исполнение, где и ждет окончания работы функции-хронометра. Версия о том, что асинхронно работает функция postMessage тоже проверялась (был написан код с непосредственным запуском потоков), но не прошла. Следующий вариант финального комплексного примера демонстрирует как раз требуемое поведение. В нем мы оформляем хронометр в асинхронно исполняемый блок кода, помещаемый в очередь на исполнение после кода запуска потоков, и все наши 3 потока вычислений (основной и 2 дополнительных) выполняются параллельно. Однако, поскольку вывод результатов на страницу (операции с DOM) возможен только в основном потоке, а он занят более длительными вычислениями, результаты из дополнительных потоков мы увидим только после завершения функции-хронометра:

Worker2.html

В заключение отметим, что код Worker thread’ов имеет доступ к объекту XMLHttpRequest, а потому возможны варианты с параллельной динамической подгрузкой и обработкой информации. Но с последовательным ее выводом через основной поток.