Pull to refresh

Использование V8, часть 3

Reading time 5 min
Views 3.3K
Использование V8, часть 3

Часть 3. Многопоточность, расширения и оформление кода

Часть 2 находится здесь: habrahabr.ru/blogs/development/72592

Часть 1 находится здесь: habrahabr.ru/blogs/development/72474



Многопоточность

Многопоточность реализована в V8 просто: ее нет. Вообще. Только один тред может использовать V8 в какой-то момент времени, причем «использовать» означает не обязательно компиляцию или выполнение javascript, но и все остальное — создание темплейтов, размещение объектов на стеке V8 и т.д.

Попробуем подсластить эту бочку дегтя парой ложек меда.

Данное архитектурное решение не является, разумеется, следствием недоразвитости программистов из Гугл. Основное предназначение V8 — это клиентское приложение — браузер Chrome, который на каждую отдельную вкладку запускает отдельный процесс. Этой же схемы следует придерживаться и серверному приложению. В нашем серверном продукте используются т.н. «рабочие» процессы, которые по named pipe присоединяются к основному процессу и получают сообщения на обработку. Это позволяет полностью загружать многоядерные системы и использовать для переключения задач фактически диспетчер процессов самой ОС Windows.

Время от времени в группах обсуждения V8 появляются люди, которые хотят «настоящую» многотредовость. Причем они готовы долго и упорно править исходный код Гугл. Обычно результатом этих дискуссий является следующий вывод: превратить текущий код V8 в многотредовый очень трудно и под силу только самим разработчикам Гугл. Подробнее можно узнать, например, здесь (англ. и необходимо быть участником группы Гугл v8-users): groups.google.ru/group/v8-users/browse_thread/thread/44621a6efef0104f

Однако и одним тредом V8 может распорядиться умнее. Для единоличного «захвата» V8 можно использовать обычную критическую секцию Windows, но лучше воспользоваться средством самого V8: классом Locker. Конструктор этого класса пытается «захватить» V8 (ожидая его освобождения), а деструктор «отпускает» V8. Таким образом, код использующий V8 и безопасно работающий в многотредовом приложении будет выглядеть примерно так:

{
 v8::Locker locker;
// используем V8
// ...
}
// V8 нами "отпущен"

* This source code was highlighted with Source Code Highlighter.


Проблема в том, что очень часто из javascript кода мы будет вызывать наш С++ код. Например, наш dblite класс из части 2 этой статьи работает с базой данных. Очевидно, что во время его работы V8 простаивает вообщем-то зря — ведь наш C++ код большую часть времени не взаимодействует с V8, а занимается вводом/выводом.

Так вот, хорошая новость в том, что V8 предоставляет класс Unlocker, который позволяет «отпустить» V8 на время. Класс Unlocker в конструкторе «отпускает» V8, а в деструкторе опять «захватывает» (в противоположность классу Locker). Прелесть в том, что Locker-ы можно вкладывать друг в друга и Unlocker это знает и правильно отработает, то есть освободит V8 независимо от глубины вложенности Locker-ов.

Сказанное можно проиллюстрировать исправлением нашего примера из части 2 статьи с классом dblite. Перепишем функцию Open с учетом новых знаний:

Handle<Value> Open(const Arguments& args)
{
  if (args.Length() < 1) return Undefined(); // нету параметров? отбой!
  dblite* db = unwrap_dblite(args.This()); // забираем указатель на dblite
  string sql = to_string(args[0]); // получаем строку в C++, описание to_string находится в первой части этой статьи
  bool r;
  {
   Unlocker unlocker; // освобождаем V8 для других тредов на время работы операций БД
    r = db->open(sql.data()); // выполняем C++ код работы с БД
  } // деструктор unlocker "вернет нам" V8
  return Boolean::New(r);
}

* This source code was highlighted with Source Code Highlighter.


Теперь Open() «занимает» V8 только на время взаимодействия с V8, а весь ввод-вывод может быть проделан в другом треде.

Небольшое лирическое отступление. Во время исследования возможностей Unlocker я наткнулся на баг, в результате которого простая программа, использующая V8, теряла по 100 Мб в секунду. К чести Гугловцев надо сказать, что проблема была решена очень быстро. В настоящее время в ветке trunk эта ошибка уже исправлена и issue закрыт. Подробности здесь (англ.): code.google.com/p/v8/issues/detail?id=444

V8 умеет примитивным образом сам переключаться между тредами, которые пытаются захватить единоличный доступ к V8 через Locker. Существует вызов Locker::StartPreemption(). Возможно это решение в какой-то момент окажется удобнее прочих.

Расширения и оформление кода

Критики V8 упрекают его (помимо однотредовости) и в слишком «развесистом» коде обвязки. Действительно, конструкции немного громоздкие, а Гугл предоставил это дело на откуп самим программистам. Еще более ухудшает ситуацию необходимость объявлять большое число однотипных функций обратного вызова, если делать их обычными функциями в global scope, то мешанина также обеспечена.

Простейшее решение касательно классов V8, обертывающих классы C++, может выглядеть так: объявляем специальный класс C++ — обертку. Функции обратного вызова делаем его статическими методами. Темплейт объекта нужно хранить в единственном экземпляре, так что тоже делаем его статическим. По аналогии с V8 заводим функцию New. Выглядеть это может как-то так:

class ScriptDatabase
{

// перенаправление вызовов из JavaScript в dblite

// функции
static Handle<Value> Open(const Arguments& args);
static Handle<Value> Execute(const Arguments& args);
static Handle<Value> Select(const Arguments& args);
...

// свойства
static Handle<Value> ErrorCode(Local<String> name,const AccessorInfo& info);
...

// извлечь из js-объекта obj указатель на соответствующий ему объект C++ ScriptDatabase
static dblite* Unwrap(Handle<Object> obj);

// создать новый объект на базе темплейта
static Persistent<Object> New(const char* db_name = 0);

// функция обратного вызова, создающая объект
static Handle<Value> Create(const Arguments& args);

};

* This source code was highlighted with Source Code Highlighter.


Что касается шаблонов, то здесь, разумеется, большой простор для творчества. И конечно уже есть некоторые решения. Можно посмотреть, например, проект cproxyv8: code.google.com/p/cproxyv8
Вот таким может получиться код, используя cproxyv8: code.google.com/p/cproxyv8/wiki/Usage

Существуют и другие проекты, упрощающие использование V8. Например, v8-juice: code.google.com/p/v8-juice
v8-juice предлагает API для загрузки функциональности произвольной DLL в javascript (http://code.google.com/p/v8-juice/wiki/Plugins), а также удобный способ преобразования типа V8 в C++ тип (http://code.google.com/p/v8-juice/wiki/ConvertingTypes). И кое-что еще.

Должен предупредить, что это проекты сторонних разработчиков и вы будете использовать их на свой страх и риск. Я обошелся в своем решении только кодом от Гугл, поскольку еще не наткнулся на эти расширения, когда начинал. Возможно, сейчас я бы что-то и использовал…

Tags:
Hubs:
+21
Comments 5
Comments Comments 5

Articles