Владимир написал: ДЕРЬМО это, а не инвесторы! НАКАЗЫВАТЬ надо такое дерьмо!
Представьте себе, что будет, если на бирже останутся одни правильные инвесторы. Ну вот шоп набрали прям по тобину на всю котлету и сидят. Куда тогда цена пойдет, есть предположения?
Афонькин написал: Логически я читаю так в Init() инициализируется/запускается экземпляр функции MOMENTUM()
Неа, в func сохраняется анонимная функция, которую возвращает MOMENTUM. Вот эта самая, про которую ниже.
Цитата
Афонькин написал: откуда во внутренней функции function (I, Fsettings, ds) берутся данные в ds ? !
А ниоткуда не берутся, в коде примера там всегда будет nil. Очевидно, писали универсальную функцию для скриптов и индикаторов. Передан датасорец - строит по датасорцу, не передан (как в коде примера) - строит по глобальным OHLCV, доступным в индикаторах.
Владимир написал: Я не понимаю смысла всех этих изысканий, жуткого обилия разных функций и ловли микросекунд.
Тут одни утверждают, что яйца надо с тупого конца бить, другие - что с острого, а я говорю, что вообще надо сбоку. Все вместе мы пытаемся показать, какие мы крутые программисты, какие тонкости знаем и какие трюки можем выделывать. Естественно, для того, чтобы государь узнал про нашу великую дружбу и пожаловал нас генералами.
Это не вопрос веры. Это вопрос взять и в сорцы луа посмотреть. Конкретно luaD_precall
Код
lua_unlock(L);
n = (*f)(L); /* do the actual call */
lua_lock(L);
Вы код невнимательно смотрели, там не вечная рекурсия, просто достаточно долгая. Ничего навеки виснуть не должно. Слип в виде сишной функции, конечно, я зря выбрал, сбивает с толку. Вот вам получше тестик. Запускаете как есть - видите сообщения с текущим счетчиком (чтобы не говорили, что их квик сожрал). Раскомментируете getWorkingFolder - видите сообщения об изменении счетчика во время работы колбека. Уж эта-то функция никак на обработку сообщений не влияет, она тупо делает GetModuleFileName и lua_pushstring и ничего более.
Код
local run = true
local cbmsg = nil
local counter = 0
local function hung(n)
--getWorkingFolder()
if n > 0 then
return hung(n - 1)
end
return 0
end
function OnParam()
if cbmsg then
return
end
local oc = counter
hung(1000)
local nc = counter
if oc ~= nc then
cbmsg = 'Counter changed during OnParam execution'
end
end
function OnStop()
run = false
end
function main()
local lastmsgtime = os.clock()
while run do
counter = counter + 1
local msg = cbmsg
cbmsg = nil
if msg then
message(msg)
else
local t = os.clock()
if (t - lastmsgtime) > 3 then
lastmsgtime = t
message('' .. counter)
end
end
sleep(1)
end
end
Для сомневающихся пруф. В колбеке выполняем достаточно долгую операцию и смотрим, успел ли мейн за это время поменять глобальный счетчик. Если успел, выводим сообщение. Сообщений не будет, то есть мейн не может поменять счетчик, пока не вернется OnParam. Существенно, что в колбеке нет сишных вызовов, то есть лок удерживается все время. Если раскомментировать sleep(0) (он сишный), сообщения будут появляться, т.е. мейн будет успевать прорваться к луа, пока выполняется слип.
Запуская этот скрипт, будьте готовы прибивать квик через диспетчер задач.
Код
local run = true
local counter = 0
local function hung(n)
--sleep(0)
if n > 0 then
return hung(n - 1)
end
return 0
end
function OnParam()
local oc = counter
hung(1000000)
local nc = counter
if oc ~= nc then
message('Counter changed during OnParam execution')
end
end
function OnStop()
run = false
end
function main()
while run do
counter = counter + 1
sleep(0)
end
end
У меня с патченой не было ни одного зависания/падения, гонял все тесты одновременно, перезаказывал по многу раз. Заменяешь на родную и достаточно быстро на тех же тестах что-нибудь ловишь. Кстати, патч только на колбеки, на SearchItems и потокобезопасные оставлено как есть. До них дорыться сложнее, все ж это пруф оф концепт, а не для работы.
Цитата
Старатель написал: Сейчас вышла версия 8.9. Посмотрим насколько она стабильнее.
Одним глазком заглянул, lua_checkstack(1) на месте.
Imersio Arrigo написал: Или там какая-то внутренняя кухня луа, не позволяющая одновременно двум стейтам из двух потоков работать?
Там критическая секция. Луа выполняется всегда под ней, выпускает только когда сишную функцию вызывает. Получается что от сишной до сишной строго по одному. Сами сишные могут параллельно, но обычно там через три инструкции lua_то и lua_сё, так что кроме лишних переключений контекста ничего не будет.
Если всегда его выдавать, есть решение проще - просто не ставить ловушку, результат абсолютно тот же. Большинство SEH исключений фатальными не являются, многие даже позволяют EXCEPTION_CONTINUE_EXECUTION (но с использованием транслятора так не сделать). Реально смертельных там штуки три всего, вот их точно надо выше пробрасывать без обработки.
TGB написал: Если ядер несколько, то, может быть ситуация, когда мейн и колбеки работают параллельно.
Выделю в процитированном для привлечения внимания
Цитата
в каждый момент времени с луа работает либо мейн, либо колбек.
Про сишные функции, работающие без лока, там тоже написано. Вот если вы в сишной функции луа не трогаете, то работаете параллельно, как только полезли в lua_что-нибудь, встали на локе, пока второй поток не соизволит вас пустить, то есть не вывалится тоже в сишную функцию. Написанный только на луа код будет выполняться строго последовательно короткими перебежками от сишной функции до сишной функции (библиотечные и квиковские функции тоже сишные).
Это техническая часть, как именно резать, вопрос в том, до скольких нулей резать. Выше само форматирование две строки занимает безо всяких циклов, все остальное там это кэш информации по бумагам, чтобы не дергать на каждом вызове getSecurityInfo. Если заранее известно, как обрезать, две строки и останется. Сделайте лучше, я будто против.
Порешаем вопрос радикальненько. Функция FormatPrice форматирует цену согласно справочнику инструментов, с правильным шагом цены и правильным числом знаков после запятой. Если цена указана не кратно шагу, она округляется по правилу "к ближайшему". Для неизвестных квику бумаг цена форматируется с 6 знаками после запятой.
Че делать по шагам: 1) рядом со своим скриптом создаем файл FormatPrice.lua 2) в него копипастим следующий код
Код
local defparams = { ['scale'] = 6, ['step'] = 0.000001 }
local strm = '-'
local stre = ''
local strf =
{
[0] = '%s%u',
[1] = '%s%u.%01u',
[2] = '%s%u.%02u',
[3] = '%s%u.%03u',
[4] = '%s%u.%04u',
[5] = '%s%u.%05u',
[6] = '%s%u.%06u',
[7] = '%s%u.%07u',
[8] = '%s%u.%08u',
[9] = '%s%u.%09u'
}
local pwr10 =
{
[0] = 1,
[1] = 10,
[2] = 100,
[3] = 1000,
[4] = 10000,
[5] = 100000,
[6] = 1000000,
[7] = 10000000,
[8] = 100000000,
[9] = 1000000000,
}
local seccache = {}
local function SecCacheMiss(classcode, seccode)
local t = getSecurityInfo(classcode, seccode)
if not t or not t.scale then
return defparams
end
local tsec = seccache[seccode]
if not tsec then
tsec = {}
seccache[seccode] = tsec
end
local tcls = tsec[classcode]
if not tcls then
tcls = { ['scale'] = t.scale, ['step'] = t.min_price_step }
tsec[classcode] = tcls
end
return tcls
end
local function GetSecParams(classcode, seccode)
local tsec = seccache[seccode]
if not tsec then
return SecCacheMiss(classcode, seccode)
end
local tcls = tsec[classcode]
if not tcls then
return SecCacheMiss(classcode, seccode)
end
return tcls
end
function FormatPrice(classcode, seccode, price)
if nil == price then price = 0 end
local prefix
if price < 0 then
prefix = strm
price = math.abs(price)
else
prefix = stre
end
local sp = GetSecParams(classcode, seccode)
local iprice, fprice = math.modf(math.floor(price / sp.step + 0.5) * sp.step)
return string.format(strf[sp.scale], prefix, iprice, math.floor(fprice * pwr10[sp.scale] + 0.5))
end
function FormatPriceFlushCache()
seccache = {}
end
Христиан написал: main будет работать в том же потоке что и квик
Поток это логическая сущность, ядро - физическая. При наличии нескольких ядер винда тоже не очень охотно перебрасывает поток на другое ядро, чтобы кэш туда-сюда не перетаскивать (посмотрите в диспетчере задач, пока одно ядро менее половины загружено, остальные почти простаивают). Да и синхронизация в луа устроена так, что мейн и колбеки в одном скрипте все равно по очереди выполняются, т.е. в каждый момент времени с луа работает либо мейн, либо колбек. Если грузите длл, сишные функции без лока выполняются, но тоже выгоду заметите, если такая функция что-то достаточно тяжелое делает без обращений к луа.
Владимир написал: exceptions надо давить в зародыше
и желательно вместе с с++ :)
С плюсами как раз проблем нет, луа его исключения ловит, стек разматывает и показывает ошибку типа "Unknown error. Possible unhandled exception". Кстати говоря, ветка для плюсовых исключений в qlua.dll не сработает никогда именно поэтому. А вот SEH исключения луа не ловит и, что важно, при этом по дефолту и стек не разматывается. А арке надо, чтобы разматывался, у них там деструкторы ждут с локами например. Чтобы разматывался, надо компилировать с /EHa и ставить транслятор, как арка и сделала. Я всего лишь предлагаю перетащить эту размотку в lua53.dll, на место преступления тксть, тогда и стейт будет оставаться живым, и qlua.dll можно без /EHa собирать, да и обработка ошибок упростится очень существенно. Либо дать уже (любому) исключению долететь до верха и пусть падает. А так как есть получается вроде изобразили размотку, на самом деле ничего толком не размотали и оставили выполняться в этом непонятном виде.
TGB написал: сигнализирующие о разрушении контекста исполнения
Да, строго говоря, в луа как таковом (непойманные) исключения возникать не должны, если они возникают, то либо а) в самом квике (вне луа), либо б) в загруженной пользователем длл. По-хорошему их вообще ловить не нужно, нужно падать, с дампом по возможности. Но луа из коробки ловит плюсовые исключения все же. Можно и в эту сторону поправить, пробрасывать не-луа исключения дальше и убрать ловушку из qlua.dll, пусть рушится, глядишь и дампы какие-то более содержательные будут получаться.
Roman Azarov написал: будет автоматически подставлена идентично худшей в стакане
В том стакане, что юзеру показывают, урезанном то есть? В этом случае, получается, наставив 50 единичек на каждом уровне, можно такую псевдорыночную заявку заставить отмениться до того, как она доберется до "настоящих" объемов. Или худшая цена из ордерлога берется?
_sk_ написал: Интересно, разработчики прислушаются или нет?
У них могут быть какие-то свои дополнительные соображения, о которых мы не знаем. Посмотрим. Если вышеупомянутый lua_checkstack(1) это явно недосмотр (вообще-то, уверен, они наступили на граблю, подложенную разработчиками луа в виде неработающего EXTRA_STACK), то здесь могут быть нюансы.
Георгий написал: Нашел один вариант, добавить переменную, которая будет сама считать кол-во сделок с нуля и вызывать в функции getitem эту переменную, но все равно кажется, что можно проще
Откройте ж инструкцию уже, или так и будете методом тыка все изучать
В паре веток понаписал свое фи по поводу обработки исключений в qlua.dll, как она мне представилась через окошко отладчика. Напомню вкратце, вокруг lua_pcallk, через которую qlua вызывает пользовательские колбеки, построен try-catch блок и перед вызовом lua_pcallk устанавливается SEH-транслятор (превращающий SEH-исключения в плюсовые). Проблема в том, что исключение (плюсовое или транслированное), пролетая мимо lua_pcallk, пропускает всю его обработку после ошибки и, таким образом, стейт после этого уже нежизнеспособен. В арке последнее понимают и в случае ошибки, пролетевшей мимо lua_pcallk, тут же прибивают скрипт.
Сказавши А, хочу добавить и Б, а именно, как можно все это покрасивее организовать. Очевидно, ловить и транслировать исключения надо не в qlua.dll (там уже поздно), а в lua53.dll. Это не значит, что надо переделывать луа, его надо просто кастомизировать имеющимися в нем средствами, а именно через luaconf.h. Хочу показать, как я это сделал у себя и предложить арке посмотреть, не лучше ли будет так.
Скрытый текст
Идея простая, мы кастомизируем LUAI_TRY, что предусмотрено разработчиками луа, добавляя туда SEH-транслятор и ловушку исключений известного нам типа, а луа-ошибки (тоже исключения типа, известного только луа) оставляем как есть. Пойманные "свои" ошибки мы превращаем в луа-ошибки, так что lua_pcallk их поймает как родных и вся обработка на стороне qlua.dll сведется к получению обычной луа-ошибки с луа-стека и выводу сообщения (ну а в мейне еще и прибитию скрипта, раз уж так принято). Реализация достаточно простая и не требует вмешательства в код луа, кроме предназначенного как раз для этого luaconf.h.
В конце luaconf.h имеется секция для наших хотелок, вот так она выглядит у меня (не относящееся к делу убрал):
Код
/* =================================================================== */
/*
** Local configuration. You can use this space to add your redefinitions
** without modifying the main part of the file.
*/
/* =================================================================== */
/* SEH and C++ exceptions should be converted to lua errors
/* =================================================================== */
struct luasehtrans
{
luasehtrans() throw();
~luasehtrans();
static void onex(void * s) throw();
private:
// disallow copy/move
luasehtrans(const luasehtrans &);
luasehtrans(luasehtrans &&);
luasehtrans & operator=(const luasehtrans &);
luasehtrans & operator=(luasehtrans &&);
void * _bkp;
};
#define LUAI_THROW(L,c) throw (c)
#define LUAI_TRY(L,c,a) \
try { ::luasehtrans st; a } \
catch(...) { if ((c)->status == 0) { ::luasehtrans::onex(L); (c)->status = -1; } }
#define luai_jmpbuf int
Также добавлен файл с реализацией класса luasehtrans:
Код
#include "luaconf.h"
#include "lua.h"
#include "lobject.h"
#include <string>
#include <stdexcept>
#include <eh.h>
#include <windows.h>
struct sehex
{
sehex(struct _EXCEPTION_POINTERS * pep) throw();
const std::string & str(void) const throw();
private:
std::string _str;
};
static const char * ecstr(DWORD ec) throw()
{
switch(ec)
{
case EXCEPTION_ACCESS_VIOLATION: return "ACCESS VIOLATION";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "ARRAY BOUNDS EXCEEDED";
case EXCEPTION_BREAKPOINT: return "DEBUG BREAKPOINT";
case EXCEPTION_DATATYPE_MISALIGNMENT: return "MISALIGNMENT";
case EXCEPTION_FLT_DENORMAL_OPERAND: return "FPU DENORMAL OPERAND";
case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "FPU DIVISION BY ZERO";
case EXCEPTION_FLT_INEXACT_RESULT: return "FPU INEXACT RESULT";
case EXCEPTION_FLT_INVALID_OPERATION: return "FPU GENERIC EXCEPTION";
case EXCEPTION_FLT_OVERFLOW: return "FPU OVERFLOW";
case EXCEPTION_FLT_STACK_CHECK: return "FPU STACK OVERFLOW";
case EXCEPTION_FLT_UNDERFLOW: return "FPU UNDERFLOW";
case EXCEPTION_ILLEGAL_INSTRUCTION: return "ILLEGAL INSTRUCTION";
case EXCEPTION_IN_PAGE_ERROR: return "PAGE ERROR";
case EXCEPTION_INT_DIVIDE_BY_ZERO: return "DIVISION BY ZERO";
case EXCEPTION_INT_OVERFLOW: return "INTEGRAL OVERFLOW";
case EXCEPTION_INVALID_DISPOSITION: return "INVALID DISPOSITION";
case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "NONCONTINUABLE EXCEPTION";
case EXCEPTION_PRIV_INSTRUCTION: return "PRIVILEGED INSTRUCTION";
case EXCEPTION_SINGLE_STEP: return "DEBUG STEP";
case EXCEPTION_STACK_OVERFLOW: return "STACK OVERFLOW";
default: return "UNKNOWN EXCEPTION";
}
}
sehex::sehex(struct _EXCEPTION_POINTERS * pep) throw()
{
static const char fmt[] = "%s at address %p";
try
{
::PEXCEPTION_RECORD per = pep->ExceptionRecord;
if(per)
{
const char * pen = ecstr(per->ExceptionCode);
int len = _scprintf(fmt, pen, per->ExceptionAddress);
if(len++ > 0)
{
char * pbuf = static_cast<char *>(_alloca(len));
if(pbuf)
{
sprintf_s(pbuf, len, fmt, pen, per->ExceptionAddress);
_str.assign(pbuf, len - 1);
}
}
}
}
catch(...)
{
// we shouldn't show to the user a garbage string
_str.clear();
}
}
const std::string & sehex::str(void) const throw()
{
return _str;
}
static void translator(unsigned int, struct _EXCEPTION_POINTERS * pep)
{
throw sehex(pep);
}
luasehtrans::luasehtrans() throw()
: _bkp(_set_se_translator(::translator))
{
}
luasehtrans::~luasehtrans()
{
_set_se_translator(static_cast<_se_translator_function>(_bkp));
}
void luasehtrans::onex(void * vs) throw()
{
try
{
lua_State * s = static_cast<lua_State *>(vs);
std::exception_ptr pe(std::current_exception());
if(!(nullptr == pe))
{
try
{
std::rethrow_exception(pe);
}
catch(const sehex & e)
{
luaO_pushfstring(s, "%s", e.str().c_str());
}
catch(const std::exception & e)
{
luaO_pushfstring(s, "%s", e.what());
}
catch(...)
{
luaO_pushfstring(s, "unrecognized exception");
}
}
}
catch(...)
{
}
}
Тксть вот и все, а ты боялась. Теперь lua_pcallk ловит и плюсовые, и SEH, и свои луа ошибки и выдает их на-гора единым принятым в луа способом, со строкой-описанием на вершине луа-стека.
Старатель, а вы тоже наблюдаете такой эффект: если много этих тестовых скриптов запущено, таблица всех сделок не успевает обновляться, перерисовывается изредка сразу с большим шагом. Но если кликнуть на каком-то меню, чтобы оно вылезло, таблица начинает рисоваться как обычно. Или открыть какой-нибудь модальный диалог, вроде заказа данных.
На новом джуне и ваших тестах получил кучку дампов и одно зависание с полпинка. После замены клуа на патченую перезаказываю уже который раз и пока все без эксцессов.
Для нехакеров замечу, что патченая длл для продакшена не предназначена, она патчена наскоро кое-как, чисто для тестов. Правильную длл сделает арка.
Чет увлекся деталями и краткого ответа по существу не написал. В общем, если ошибка (любая) была поймана pcall'ом, никаких разрушений в квике нет. Если поймана костыльной ловушкой, разрушения есть. Второе не значит, что дальше обязательно зависнет, все может быть хорошо, разве что утечет сколько-нибудь памяти (вряд ли много). В колбеках разница видна снаружи, пойманные pcall'ом ошибки скрипт не останавливают, только выводят сообщение, пойманные ловушкой - останавливают. С мейном сложнее, скрипт прибивается в любом случае. Сообщение ACCESS VIOLATION говорит о том, что поймала ловушка, типовое луа-сообщение типа не есть функция и подобные - это из pcall.
Старатель написал: И в какой-то момент вместо данных там оказался nil. Скрипт остановился. Это может повлиять на будущую работу?
В идеальном мире не должно, квик вызывает скрипты и колбеки под pcall, такие ошибки должны им отлавливаться и правильно обрабатываться. В реальном мире квик не всегда проверяет, когда ему из скрипта передан nil или битый указатель, лезет по ним и получает по ушам. Недавняя тема тому пример. Эти ошибки почему-то пролетают мимо pcall, в качестве костыля арка настроила вокруг pcall'а try-catch блок, и если ошибка долетела до него, скрипт принудительно прибивается прямо в ловушке. Проблема в том, что в этом случае вся постобработка в pcall обходится, то есть стек не выравнивается (одна из причин, видимо, почему в арке решили тут же скрипт прибивать, он уже и так не жилец), но главное не выпускается лок. С одной стороны, у каждого скрипта лок свой, раз скрипт прибили, то и проблем быть не должно, остался ли он захваченным или нет. С другой можно придумать (и изредка пронаблюдать) сценарий, в котором квик уже после прибития скрипта попытается что-то проделать в том же стейте, соответственно попробует захватить лок и повиснет на нем (а выпускать его уже некому). Это проблема отдельная, по-хорошему от костыля с ловушкой надо избавляться, а сначала расковырять все места, где квик наступает на мину в виде нила или битого указателя.
Старатель написал: А логические ошибки, когда в непредсказуемом месте вылезает nil, типа attempt to compare number with nil и т.п., они могут как-то в будущем повлиять? QUIK перегружать нужно?
Когда после ошибки в одном скрипте начинают сыпаться ошибки в других, или в нем же после рестарта, это показывает, что стек был покоцан, дальше уже как повезет, насколько сильно и т.д., где-то закончится небольшой утечкой, где-то уже серьезные повреждения, которые вылезут позже.
PS Не поделитесь содержимым ip.cfg от джуниора? Чет я его в виде архива не найду.
Старатель написал: Не, знаю, после долгого тестирования, QUIK ошибки накапливает или чё?
Каждый слот стека начинается с GCObject, а его первое поле это GCObject * next. То есть все луа-объекты связаны в списки, используемые коллектором для прибития зомбаков. Раз уж вы словили разрушение стека, все эти списки коллектора уже сломаны, так что он начнет творить непредсказуемые вещи. Плюс, вылезая за границы выделенной памяти (и не будучи пойманным), луа ломает также и списки аллокатора, так что опять же когда-то потом в непредсказуемом месте (может быть, через пару дней) случится внезапный крэшик, концов которого не найдешь.
ДокладАю. Приведенный здесь тест у меня не воспроизвелся, воспользовался тестом из этого поста, он у меня хорошо воспроизводится. На оригинальной qlua.dll вывалился с дампом (все того же вида) с первого перезаказа. На патченой четыре перезаказа (вот сейчас, на большой ТВС) отработал, полет нормальный. Особо нагружать боевой сервер я стесняюсь, так что для сбора статистики предлагаю заинтересованным коллегам повторить эксперименты у себя. Если подтвердится работоспособность в патченом виде, то причина разрушения луа-стека в inline-обертке lua_checkstack внутри qlua.dll, там захардкодили расширение стека на 1 слот. Патч меняет это значение на 256 и в одном месте на 63 (не хватило байт для опкода).
Разобрался и с OnParam, это там компилятор начудил своих оптимизаций, у арки было 1 как везде, гыгы. Как оказалось, я не 16, а 256 везде понаставил, ну даже и лучше, чай не на контроллере выполняемся. Тут вариант 2 с патчем и на OnParam тоже.
Старатель, не желаете патченую qlua.dll запробовать со своими тестами? Заменил там все lua_checkstack(1) на lua_checkstack(16), кроме OnParam, с этой сходу не понял даже, что там вытворено такое, выглядит как (в том фильме). Если паранойя не мучает, качнуть можно здесь. У меня нынче брокера нет на связи, оффлайн я не могу ваши крэши воспроизвести, а без крэшей что с патчем, что без, работает одинаково.
Дабы не загаживать прочие темы своими наблюдениями, создам себе отдельную. Цели получить ответ от сотрудников арки не ставлю, но если лишний раз глянут в свой код и убедятся, что заданный вопрос всего лишь ошибка наблюдений, или, паче чаяния, поправят что-нибудь, никому хуже не станет. Если в какой-то момент арка решит, что вопросы здесь излишне интимные, фил фри, как говорится, прибить всю ветку без размышлений, никто не обидится.
Для начала наблюдение номер один. Где-то в глубине qlua.dll готовится вызов OnAllTrade. Как положено, вызывается lua_checkstack. Но погодите, вызывается lua_checkstack(state, 1) и тут же на стек кладутся ДВА элемента, сама функция и ее аргумент. Кроме того, в процессе заполнения таблицы еще и временные элементы кладутся. Все ли верно, нигде ли стек не испортится?
Аналогичное наблюдение можно сделать при вызове (видимо) любого колбека с аргументом. OnConnected, OnDepoLimit, OnDepoLimitDelete, OnFirm, ...
Виктор написал: если не использовать ref() при вызове функции в новый поток, то значение передаётся по значению
Конструктор делает move() того, что ему дали. Подразумевается, что передается именно плюсовый объект, что он отработает мув и потом деструктор в основном потоке увидит, что прибивать уже нечего. Или POD (но не указатель), где по барабану на мув. С сишным указателем, например, будут проблемы, это та же ссылка вид сбоку.
А вообще поток создавать стоит только если ожидается хотя бы пара секунд его работы, там очень существенный оверхед. Под мелкие задачи лучше поток из пула брать (тем же async хотя бы).