Threadvar в Delphi работает нормально, и используется в FreeUDFLib. Однако совсем недавно было обнаружено, что на многопроцессорных компьютерах использование threadvar может привести к "падению" dll и скорее всего ibserver.exe. Чаще всего это происходит если объявить "объектный" тип как threadvar. В таком случае более надежной является прямая работа с TLSAlloc, TLSFree, TLSGetValue, TLSSetValue (и т.д.).
Данная проблема также была упомянута в Delphi Informant (август).
Хоть уважаемый James Arias-La Rheir и говорит, что функции с RETURNS PARAMETER
лучше не использовать, я решил протестировать все варианты передачи и возврата строковых параметров.
Вообще способов может быть три:
1. возврат строки при помощи входного параметра – RETURNS PARAMETER. Этот способ не описан в документации и несколько неочевиден, т. к. вместо функции необходимо писать процедуру. Основное неудобство – хранимое в БД объявление функции не совпадает с объявленным, т. е. функция после создания выглядит как обычная функция с возвращаемым в виде строки параметром (а не номером параметра).
2. возврат строки, созданной внутри UDF при помощи malloc (FREE_IT). Этот способ с точки зрения производительности самый худший, поскольку память аллокируется и уничтожается при каждом вызове функции. Если функция обрабатывает значения 10000 записей, значит те же 10000 раз память будет захвачена и освобождена. С другой стороны, скорость таких операций намного выше, чем скорость получения записей с диска, поэтому влияние на скорость обработки данных будет минимальным. Обязательным является соответствие количества аллокируемой памяти внутри функции, и длина строки в объявлении функции в БД.
3. возврат строки, которая является входным параметром. Это "трюк", поскольку IB сам аллокирует память для входного параметра. Основные неудобства – размер выходной строки должен совпадать с размером входной, и невозможность использования этого метода если функция не содержит входных строковых параметров (или по типу эти параметры отличаются).
В случаях 1 и 3 память для передачи и получения результата UDF выделяется IB один раз в контексте клиентского соединения.
Для каждого случая была написана функция (см. функции function1, 2 и 3 соответственно в файле safeudf.zip). Тестировались функции следующим образом: из 3-х экземпляров Database Explorer одновременно выполнялся запросSELECT FUNCTION1(string) FROM EMPLOYEE, EMPLOYEE
WHERE FUNCTION1(string) <> stringгде string – уникальная строка для каждого экземпляра Database Explorer, но одинаковой длины (10 символов). В том случае, если условие FUNCTION1(string) <> string по каким-то причинам нарушалось, ответ на запрос содержал бы минимум 1 строку. Это могло произойти только если результат функции перекрывался бы другим результатом функции. Такого у thread-safe функций произойти не может в принципе.
Тест показал абсолютную безопасность всех трех способов при работе в многопользовательских средах.
Временные замеры показали, что быстрее всех выполнялась функция1 (4 мин 10 сек), чуть медленнее – функция3 (4 мин 17 секунд), и медленнее всех – функция2 (4 мин 57 секунд). Различие в скорости выполнения функций не является пропорциональным объему обрабатываемых данных. Поэтому можно сказать, что все три варианта могут быть успешно использованы.