Есть код, который выполняется в main и в callback'ах Lua. Задача - обеспечить доступ к разделяемому ресурсу. Допустим, доступ к разделяемому ресурсу осуществляется с помощью C++ std::recursive_mutex или критических секций (WinAPI).
Например, пусть один поток добавляет элемент в таблицу, а другой читает её. Сам вопрос в коде.
Код
-- положим, есть внешняя библиотека, которая создаёт userdata для std::recursive_mutex в lua
-- mtx:lock() вызывает std::recursive_mutex::lock() (или EnterCriticalSection, если используются критические секции вместо recursive_mutex)
-- mtx:unlock() вызывает std::recursive_mutex::unlock() (или LeaveCriticalSection, если --//--)
mtx = recursive_mutex.create();
tbl = {};
function OnParam(class_code, sec_code)
local idx = class_code .. "|" .. sec_code;
mtx:lock();
tbl[idx] = (tbl[idx] or 0) + 1;
mtx:unlock();
end
running = true;
function OnStop()
running = false;
end
function main()
while (running) do
sleep(50);
mtx:lock();
local str = {};
-- ВОПРОС: Будет ли в этом месте таблица tbl содержать актуальные значение?
-- Допустим, только что выполнился вызов mtx:unlock() в OnQuote в callback'е и мы попали сюда в потоке main.
-- Отразится ли изменение выполненное в потоке callback'а строкой кода "tbl[idx] = (tbl[idx] or 0) + 1"
-- на таблице tbl в потоке main?
-- Как добиться здесь актуального значения полей таблицы tbl в этом месте?
for k,v in pairs(tbl) do
str[#str+1] = k .. "=" .. v;
end
message(table.concat(str, "\n"));
mtx:unlock();
end
end
Пример общий. Планируется не только добавлять строки в таблицу, но и удалять, и изменять. И не только в одну таблицу. Прошу дать ответ для последней версии Quik и для версии Quik 6.17.3.6.
для начала можно почитать документацию QLUA где указано: ---------------------------------- Потокобезопасные функции для работы с таблицами Lua Одновременная работа с таблицами из функций обратного вызова скрипта и функции main() может приводить к неопределенным ситуациям. Для решения этой проблемы qlua.dll предоставляет потокобезопасные аналоги стандартных функций Lua. Формат вызова потокобезопасной функции совпадает с форматом вызова аналогичной стандартной функции Lua. В таблице представлены стандартные функции Lua и соответствующие им потокобезопасные аналоги:
Потокобезопасные функции для работы с таблицами Lua
Одновременная работа с таблицами из функций обратного вызова скрипта и функции main() может приводить к неопределенным ситуациям. Для решения этой проблемы qlua.dll предоставляет потокобезопасные аналоги стандартных функций Lua. Формат вызова потокобезопасной функции совпадает с форматом вызова аналогичной стандартной функции Lua. вместо concat ->sconcat remove ->sremove insert->sinsert sort->ssort
Николай Камынин, вопрос немного не об этом. Я уже выполняю синхронизацию на уровне мьютексов. Вопрос в том, прочитаются ли данные в потоке main сразу после вставки их в таблицу в обработчике callback'а или наоборот.
Перечисленных потокобезопасных функций мне не достаточно. В частности, мне нужно пройтись по таблице, в которой добавляются/удаляются элементы из callback'а и выполнить определённые действия для некоторых элементов по условию.
@тех.поддержка: При необходимости, для тестов, могу выслать код библиотеки, которая обслуживает userdata для критических секций. Вопрос по большей части технический. Почти уверен, что так как я делаю, делать можно. Хотелось бы услышать подтверждение от команды Quik'а.
Вячеслав, то как вы делаете, на мой взгляд в общем-то идеально. Не могу только понять при чем тут версия квика, от неё точно ничего здесь не зависит.
Идеальность в том, что вы, с одной стороны, полностью блокируете работу с таблицей из другого потока на все время добавления в неё элемента, с другой стороны полностью блокируете добавление в таблицу на всё время перебора имеющихся элементов внутри main(). Т.е. у вас всегда гарантированно в цикле обрабатывается некое полностью статичное состояние таблицы. И это само по себе безусловно здорово.
Однако, есть важный момент. Состоит он в том, что на всё время обработки таблицы (в main()) у вас фактически полностью блокирована работа call-back'ов при наступлении события OnParam(). И тут уже возникает вопрос про эффективность такого алгоритма. В самом деле: видно, что вы пытаетесь сделать "очередь событий" в таблице, и события из этой очереди разгребаете как бы в отдельном потоке main(). Но в вашем алгоритме вы не можете положить в таблицу очередное событие из OnParam() до тех пор, пока не обработали все имеющиеся события в таблице. Т.е. у вас получится, что в таблицу попали какие-то события (в виде элементов таблицы), вы их в main() обрабатываете - и на всё время этой обработки у вас OnParam() тупо залочен, QUIK стоит и ждёт, пока вы в main() обработаете все имеющиеся элементы таблицы. Не видно, чтобы тут случился выигрыш-то от двухпоточности. У вас всё равно каждый поток постоянно ждёт другой поток, в том числе и поток добавления событий.
Что касается "Потокобезопасных функции для работы с таблицами Lua" - то они несколько про другое. Во всяком случае они не реализуют тот алгоритм, который приведён в примере. Отличие будет в том, что не будет лочения потоков на время обработки всех элементов таблицы. Подходит ли это автору вопроса - не понятно. Он ведь лочит вокруг всего цикла обработки, а не вокруг обработки одного элемента.
Вячеслав написал: Николай Камынин , вопрос немного не об этом. Я уже выполняю синхронизацию на уровне мьютексов. Вопрос в том, прочитаются ли данные в потоке main сразу после вставки их в таблицу в обработчике callback'а или наоборот.
Перечисленных потокобезопасных функций мне не достаточно. В частности, мне нужно пройтись по таблице, в которой добавляются/удаляются элементы из callback'а и выполнить определённые действия для некоторых элементов по условию.
Я делаю иначе: если потоку main делать нечего, то я его усыпляю. в результате он не занимает никаких ресурсов процессора. когда я обновляю таблицу, то пинаю поток main, он просыпается и обрабатывает то, что ему пришло в очереди. после этого он снова засыпает до нового пинка.
что-же касается простаивания колбеков, при работе main, то это можно решить путем копирования очериди в локальный массив в main. и далее main работает с копией, а колбеки с очередью
swerg написал: Вячеслав , то как вы делаете, на мой взгляд в общем-то идеально. Не могу только понять при чем тут версия квика, от неё точно ничего здесь не зависит.
Идеальность в том, что вы, с одной стороны, полностью блокируете работу с таблицей из другого потока на все время добавления в неё элемента, с другой стороны полностью блокируете добавление в таблицу на всё время перебора имеющихся элементов внутри main(). Т.е. у вас всегда гарантированно в цикле обрабатывается некое полностью статичное состояние таблицы. И это само по себе безусловно здорово.
Однако, есть важный момент. Состоит он в том, что на всё время обработки таблицы (в main()) у вас фактически полностью блокирована работа call-back'ов при наступлении события OnParam(). И тут уже возникает вопрос про эффективность такого алгоритма. В самом деле: видно, что вы пытаетесь сделать "очередь событий" в таблице, и события из этой очереди разгребаете как бы в отдельном потоке main(). Но в вашем алгоритме вы не можете положить в таблицу очередное событие из OnParam() до тех пор, пока не обработали все имеющиеся события в таблице. Т.е. у вас получится, что в таблицу попали какие-то события (в виде элементов таблицы), вы их в main() обрабатываете - и на всё время этой обработки у вас OnParam() тупо залочен, QUIK стоит и ждёт, пока вы в main() обработаете все имеющиеся элементы таблицы. Не видно, чтобы тут случился выигрыш-то от двухпоточности. У вас всё равно каждый поток постоянно ждёт другой поток, в том числе и поток добавления событий.
1. Если бы всё было идеально, я бы не спрашивал ответа на форуме. Загвоздка в том, что обычная Lua 5.1 поддерживает работу только в одном потоке, поэтому Quik использует внутри либо одну из многопоточных библиотек Lua (очень похоже на Lua Lanes), либо придерживается своей реализации. В любом случае, проблема в том, что в функции callback'а и функции main используется разные структуры lua_State*, и сразу ли значение, присвоенное в одном lua_State станет доступно в другом - вопрос к команде Quik. Интересно, что адреса всех глобальных таблиц (получаются при вызове tostring(tbl)) в функции main и callback'ах совпадают.
2. В main у меня проход по таблице, если условие для элемента выполнилось, то производится дополнительная работа, но самый максимум получается 20 мс при блокированной таблице (и то раз в 5 секунд), поэтому некритично, если на это время заблокируется главный поток Quik, а так в main я ещё выставляю транзакции (без блокирования чего-либо).
Вячеслав написал: 2. В main у меня проход по таблице, если условие для элемента выполнилось, то производится дополнительная работа, но самый максимум получается 20 мс при блокированной таблице (и то раз в 5 секунд), поэтому некритично, если на это время заблокируется главный поток Quik, а так в main я ещё выставляю транзакции (без блокирования чего-либо).
Позвольте поинтересоваться, зачем переносить обработку в другой поток, если во время обработки основной поток всё равно простаивает? Какая цель преследуется?
Надо делать так, как надо. А как не надо - делать не надо.
Старатель написал: Позвольте поинтересоваться, зачем переносить обработку в другой поток, если во время обработки основной поток всё равно простаивает? Какая цель преследуется?
К сожалению, не понял вопроса. Схематично у меня 2 потока (callback'и и main) выглядят так. Своих отдельных потоков не создаю.
Код
callback {
найти (связать со сделкой) экземпляр ордера
в случае, если весь объйм транзакции выбран полностью, отписаться в лог и удалить из списка активных транзакций
если выбранный объём изменился или произошла ошибка, отписаться в лог
}
main {
while(running) do
по алгоритму выставить нужные транзакции
по алгоритму выполнять подписку на стаканы нужных инструментов с помощью Subscribe_Level_II_Quotes
раз в 5 секунд {
блокировать список активных транзакций
проверять не истёк ли для них таймаут ожидания ответа (для связки ordernum -- trans_id)
те, для которых истёк, удалять из списка с записью в лог
}
end
}
Николай Камынин написал: т е на какой момент извлеченный в main элемент будет первым в таблице, если колбеке есть вставка первого элемента?
Таблица, как известно, в QLua всегда целостна. Не зависимо от многопоточного обращения. Целостна в том смысле, что в любой момент времени количество элементов, вычитываемое через получения размера в таблице гарантированно совпадает с реальным количеством элементов на момент вычитывания значения счётчика.
Потому не очень понятен ваш вопрос. Если на момент обращения элемент в таблице уже размещён - то произойдёт корректное обращение к элементу таблицы, если же элемент еще не размещен - то его и не будет!
Единственное тонкое место, как известно из предыдущих обсуждений, состоит лишь в том, что если в одном потоке у нас есть t[1] = 5 (поток 1) а в другом потоке есть x = t[1] (поток 2) и они происходят "одновременно", то возможны варианты:
а) x = t[1] приведёт к ошибке, т.к. на самом деле в потоке 1 этот код еще вовсе не начал выполняться и элемент еще не добавлен; однако тогда и счётчик нам скажет, что количество элементов еще 0 (если это первый элемент)
б) после выполнения x = t[1] не будет ошибки, но в x окажется nil; это произойдёт в том случае, если в потоке 1 уже успеет добавиться новый элемент в таблицу с индексом 1, однако само значение ему еще не присвоится; (хотелось бы отметить, что само состояние таблицы будет при этом совершенно корректным! т.е. количество элементов в таблице равно 1, это единственный элемент равен nil)
в) в результате выполнения после x = t[1] в x будет значение 5, т.е. к моменту выполнения этого кода в рамках потока 2 в потоке 1 уже успел и новый элемент в таблицу добавиться, и значение ему присвоиться.
Вячеслав написал: используется разные структуры lua_State*
Это вот откуда такой вывод?
Цитата
Вячеслав написал: и сразу ли значение, присвоенное в одном lua_State станет доступно в другом
Конечно сразу. Вы и сами легко это можете проверить, добавляя в колбеке за раз 100 (или тысячу) элементов одном потоке, и тут же вычитывая их в main(). Новые элементы будут видны в main еще до завершения колбека.
Распечатал адрес lua_State* приходящий в функции при вызове из main и из главного потока. Очень похоже, что главный поток создаётся с помощью lua_State* global = lua_newstate(...); a main - с помощью lua_State *main = lua_newthread(global);
удостоверился в этом, выведя на печать таблицу LUA_REGISTRYINDEX. LUA_REGISTRYINDEX для потока main хранит объект thread. При этом для главного потока LUA_REGISTRYINDEX в момент инициализации пустая.
P.S. Подозреваю, что в QLua просто переопределили lua_lock и lua_unlock, чтобы разрешить многопоточность. Сейчас выясняю, как выполняется синхронизания глобальных переменных в Lua между потоками при переопределённых lua_lock / lua_unlock.
Вячеслав написал: удостоверился в этом, выведя на печать таблицу LUA_REGISTRYINDEX. LUA_REGISTRYINDEX для потока main хранит объект thread. При этом для главного потока LUA_REGISTRYINDEX в момент инициализации пустая.
Ошибся. LUA_REGISTRYINDEX для state'ов созданных с помощью lua_newthread совпадает с глобальным. Т.о. поток lua_newthread создаётся после интерпретации всего скрипта.
QLua использует 1 мьютекс для разделения выполнения функции main и callback'ов или 2? Другими словами, lua_lock / lua_unlock работают с одним и тем же мьютексом, если вызваны из main или из callback'а?
Если 1, то всё становится на свои места: получается, что параллельно никакой Lua-код не выполняется (любая C функция, как то sleep - Lua-кодом не является). Если используются 2 мьютекса, то у меня по-прежнему вопрос, как происходит синхронизация глобальных Lua-переменных при выполнении двух Lua VM (для глобального потока и потока main) в разных системных потоках параллельно?
Подчеркну, что такое знание не может являться "конфеденциальным", т.к. для разработки сложных Lua-плагинов для Quik нужно иметь возможность понимать, как меняются глобальные Lua-переменные из потока main и из главного Lua-потока (для callback'ов).
Вячеслав написал: P.S. Подозреваю, что в QLua просто переопределили lua_lock и lua_unlock, чтобы разрешить многопоточность. Сейчас выясняю, как выполняется синхронизания глобальных переменных в Lua между потоками при переопределённых lua_lock / lua_unlock.
Вы так пишете, как-будто есть другие способы. Про это уже писалось (на старом форуме, как водится).
Вячеслав написал: QLua использует 1 мьютекс для разделения выполнения функции main и callback'ов или 2?
Вы задаёте просто удивительные вопросы, ну вот честно. Ради любопытства, можете сказать, как вы предполагаете использовать 2 мьютекса для синхронизации между потоками? ну вот чисто теоретически? А если это никак - то о чем вопрос??
Вячеслав написал: QLua использует 1 мьютекс для разделения выполнения функции main и callback'ов или 2?
Вы задаёте просто удивительные вопросы, ну вот честно. Ради любопытства, можете сказать, как вы предполагаете использовать 2 мьютекса для синхронизации между потоками? ну вот чисто теоретически? А если это никак - то о чем вопрос??
Понадобится более двух. По одному на доступ к VM каждого из потоков, а также мьютексы для доступа к переменным LUA_REGISTRYINDEX и LUA_GLOBALSINDEX, + по одному мьютексу на каждую таблицу и userdata. Да, возможно было бы глупо с моей стороны считать, что в Quik'е решились на подобный рефакторинг движка Lua.
Николай Камынин написал: У меня вопрос к автору темы. А зачем использовать мьютекс, если у нас один процесс? Спасибо
Даже не знаю с чего начать. В Windows "процесс" - это контейнер, в котором выполняются потоки (нити). Потоки имеют состояние выполнения, которое включает регистры процессора и стек. Адресное пространство (память) у потоков одного процесса общая. Для синхронизации доступа к ресурсам, которые используются несколькими потоками используют мьютексы.
swerg, Подытожу: в Lua переключение потоков (глобального на main и обратно) происходит только в момент вызова любой Си функции (любой библиотечной функции). В остальное время, если Lua-код выполняется, то он выполняется атомарно. Поэтому же Quik можно подвесить простым кодом
Код
while (true) do end
Поставив его, например, в main и пусть в этот момент Quik захочет вызвать какой-нибудь callback. В результате, Quik зависнит на lua_lock Си функции, т.к. Lua VM будет в этот момент занята потоком main, выполняющим наш бесконечный цикл.
Вячеслав написал: Поэтому же Quik можно подвесить простым кодомКодwhile (true) do end Поставив его, например, в main и пусть в этот момент Quik захочет вызвать какой-нибудь callback. В результате, Quik зависнит
Николай Камынин написал: У меня вопрос к автору темы. А зачем использовать мьютекс, если у нас один процесс? Спасибо
Даже не знаю с чего начать. В Windows "процесс" - это контейнер, в котором выполняются потоки (нити). Потоки имеют состояние выполнения, которое включает регистры процессора и стек. Адресное пространство (память) у потоков одного процесса общая. Для синхронизации доступа к ресурсам, которые используются несколькими потоками используют мьютексы.
Я собственно спросил зачем мьютекс если один процесс, в том плане, что в одном процессе синхронизацию потоков эффективнее делать другими средствами либо в пользовательском режиме -атомарным операциями и критическими секциями либо ядерными - событиями. Это быстрее , чем мьютекс.
Вячеслав написал: Поэтому же Quik можно подвесить простым кодомКодwhile (true) do end Поставив его, например, в main и пусть в этот момент Quik захочет вызвать какой-нибудь callback. В результате, Quik зависнит
нет не зависнет
Тогда в какой момент произойдёт переключение выполнения потока main на выполнение callback'а? Ведь в main таким кодом мы монопольно занимаем Lua VM.
Николай Камынин написал: У меня вопрос к автору темы. А зачем использовать мьютекс, если у нас один процесс? Спасибо
Даже не знаю с чего начать. В Windows "процесс" - это контейнер, в котором выполняются потоки (нити). Потоки имеют состояние выполнения, которое включает регистры процессора и стек. Адресное пространство (память) у потоков одного процесса общая. Для синхронизации доступа к ресурсам, которые используются несколькими потоками используют мьютексы.
Я собственно спросил зачем мьютекс если один процесс, в том плане, что в одном процессе синхронизацию потоков эффективнее делать другими средствами либо в пользовательском режиме -атомарным операциями и критическими секциями либо ядерными - событиями. Это быстрее , чем мьютекс.
Я не понял контекст вашего оригинального сообщения. Есть просто термин мьютекс, есть std::mutex (или std::recursive_mutex - по сути реализованые через аналог критических секций на уровне Concurrency namespace'а в MS CRT), и есть WinAPI mutex, который может использоваться несколькими процессами сразу. В первом сообщении я сразу упомянул, что имею ввиду std::recursive_mutex или критические секции, а дальше уже оба варианта называл просто "мьютексом", чтобы не писать одно и то же по нескольку раз.
Я не понял контекст вашего оригинального сообщения. Есть просто термин мьютекс , есть std::mutex (или std::recursive_mutex - по сути реализованые через аналог критических секций на уровне Concurrency namespace'а в MS CRT), и есть WinAPI mutex , который может использоваться несколькими процессами сразу. В первом сообщении я сразу упомянул, что имею ввиду std::recursive_mutex или критические секции, а дальше уже оба варианта называл просто "мьютексом", чтобы не писать одно и то же по нескольку раз.
судя по ответу для Вас не имеет разницы мьютекс и критическая секция. Но основное их отличие - это быстродействие. у мьютексов малое, у критических секций -высокое (согласно Рихтеру) ----------------------------------------- мьютекс - это полноценный объект ядра, поэтому он и медленный его имеет смысл применять, если надо синхронизировать потоки различных процессов. в данном случае процесс один. Поэтому надобность в мьютексе не очевидна. вернее сказать избыточна. ------------------------------------------------------ Зачем из пушки стрелять по воробьям. Но это я так для дискуссии, типа если мьтексом обозвать все, то я применяю 5 мьютексов, которые в действительности 4 interlocked-функции и 1 event. ---------------------------- Поэтому применение мьютексов излишне в КВИКЕ.
Предполагаю, что QLua использует отладочные функции debug.sethook для перехвата управления и переключения Lua VM c потока main на поток callback'ов. Других варинтов не вижу, как можно (без переписывания самого движка Lua) переключить Lua VM на выполнение другого потока даже в случае бесконечного цикла
Код
while(true) do end
(кроме того, нашёл код в Lua: lvm.c макрос dojump, который как раз и будет выполняться в случае с безусловным переходом, и который выполняет unlock / lock, поэтому бесконечный цикл точно не заблокирует Lua VM навсегда, а с использованием debug.sethook заблокировать Lua VM навсегда в принципе становится невозможно)
В таком случае, лишь одна команда Lua VM выполняется атомарно, а затем может произойти переключение на выполнение другого Lua потока. Отсюда следует, что использование библиотеки, реализующей API доступа к системным мьютексам (или критическим секциям, как будет угодно), будет вполне оправдано для разграничения доступа к глобальным Lua таблицам.
Не ожидаю увидеть заметную потерю производительности в этом случае, т.к. QLua на каждую Lua команду выполняет unlock / lock мьютекса, ограничевающего доступ к Lua VM (через обращение к callback'у, установленному с помощью debug.sethook).
макросы lua_lock и lua_unlock - уже давно реализованы через критические секции. Так советовалось ещё на страницах "lua-users wiki", а также не раз подтверждалось самими разработчиками. Использование мьютексов конкретно в данном контексте - т.е. как замена критическим секциям в вышеуказанных макросах - глупая идея. Что - тоже не раз уже упоминалось.
Более того, на страницах форума уже было расписано во всех деталях все тонкости реализации многопоточности в квике - пользуйтесь гуглом и обрящите.
Вячеслав написал: Предполагаю, что QLua использует отладочные функции debug.sethook для перехвата управления и переключения Lua VM c потока main на поток callback'ов.
В QLua используются реальные разные потоки операционной системы Windows. Потоки переключает Windows, где реализована вытесняющая мультизадачность. Бесконечные циклы не мешают Windows переключать процессор между исполнением кода разных потоков, никакая Lua VM тут ни при чем. Что вы еще пытаетесь найти в Lua VM?
И да, код в смысле Lua-программы прерывается в реализованном случае вовсе не обязательно "между операторами Lua". Прерваться код может и "посередине выполнения одного из операторов".
swerg написал: И да, код в смысле Lua-программы прерывается в реализованном случае вовсе не обязательно "между операторами Lua". Прерваться код может и "посередине выполнения одного из операторов".
Да, выполнение любого потока может прерваться в любом месте. Что я имею ввиду, так это то, что 2 Lua-команды (из потока main и потока callback'а) одновременно выполняться не могут: если выполнение потока main будет приостановлено системным планировщиком на середине выполнения Lua-команды (luaV_execute) и в этот момент начнёт выполняться основной поток Quik, который захочет вызвать наш callback, то основной поток Quik будет ждать на lua_lock операции (планировщик переключит выполнение на другой поток вместо основного потока Quik), т. к. поток main в данный момент владеет мьютексом, который предотвращает одновременное выполнение двух Lua-комманд из разных потоков.
Вячеслав, у вас есть пруф подтверждающий то, что вы сейчас написали, или это ваши фантазии? Как-то всё очень сомнительно выглядит, особенно вот это
Цитата
Вячеслав написал: и в этот момент начнёт выполняться основной поток Quik, который захочет вызвать наш callback, то основной поток Quik будет ждать на lua_lock операции
swerg, давайте по порядку. Чтобы вызвать любую Lua-функцию из Си-кода нужно вызвать lua_call или lua_pcall. Остановлюсь на втором методе, т.к. именно его использует Quik для вызова main, всех callback'ов и загрузки скрипта (через Lua-функцию require или dofile). Вызов lua_pcall выполняет lua_lock. Также напомню, что lua_lock / lua_unlock работают с одной и той же критической секцией, которая обслуживает сразу два потока (main и callback'ов), поэтому, если в другом потоке эта критическая секция будет заблокирована, то поток, вызывающий lua_lock, будет ждать её освобождения. Итак, в lua_pcall мы блокируем нашу критическую секцию lua_lock, затем по цепочке вызовов lua_pcall -> luaD_pcall -> luaD_rawrunprotected -> LUAI_TRY (этот макрос вызывает на самом деле f_call - см. параметр ф-ции luaD_rawrunprotected) -> luaD_call -> мы наконец попадаем в luaV_execute (в случае, если наша функция - это Lua-функция). Замечу, что всё это время мы удерживаем критическую секцию (lua_lock). Освободить критическую секцию, пока выполяется luaV_execute мы можем по трём причинам (см. код luaV_execute): 1. dojump использует luai_threadyield, который позволяет переключиться на выполнение другого потока, 2. luaD_precall в случае вызова Си-функции из Lua-функции (например Sleep), 3. traceexec выполняется на каждой итерации LuaVM и, в случае если установлен debug.sethook, luaD_callhook выполнит lua_unlock / lua_lock.
Также замечу, что LuaVM (luaV_execute) не использует LuaAPI (функции из lapi.c) для доступа к переменным. LuaVM использует внутренние функции (которые как раз и вызывают функции из lapi.c) напрямую - без блокировки критической секции (lua_lock / lua_unlock), потому что, когда выполняется luaV_execute, критическая секция уже заблокирована.
Да, создаются 2 системных потока, да, в каждом из них выполняется lua_pcall, который приводит к вызову luaV_execute, но пока выполняется код самой luaV_execute, код в другом потоке ждёт на lua_lock или выполняет что-нибудь другое.
P.S. Теоритически, чтобы заставить LuaVM долго не отпускать критическую секцию нужно выполнить debug.sethook(nil), а затем выполнять в одном из потоков линейный Lua-код, который не приведёт к вызову dojump.
имеется ввиду, что функции из lapi.c вызывают внутренние функции в библиотеке Lua, и их же вызывает luaV_execute напрямую. Например, lua_settable вызывает luaV_settable, которую напрямую (через макрос) вызывает luaV_execute.
имеется ввиду, что функции из lapi.c вызывают внутренние функции в библиотеке Lua, и их же вызывает luaV_execute напрямую. Например, lua_settable вызывает luaV_settable , которую напрямую (через макрос) вызывает luaV_execute.
Причем здесь вызов функций? Синхронизировать надо обращение к данным, а не к коду функций. Вы что-то путаете.
Вячеслав написал: если выполнение потока main будет приостановлено системным планировщиком на середине выполнения Lua-команды (luaV_execute) и в этот момент начнёт выполняться основной поток Quik, который захочет вызвать наш callback, то основной поток Quik будет ждать на lua_lock операции (планировщик переключит выполнение на другой поток вместо основного потока Quik), т. к. поток main в данный момент владеет мьютексом, который предотвращает одновременное выполнение двух Lua-комманд из разных потоков.
1. по понятным причинам, не ручаюсь на 100% но, код работы с коллбеками, завязан на отдельный LUA-поток в контексте одной виртуальной машины. Другой поток (и физический и LUA-поток) - это, ещё один коллбек - функция "main". Таким образом, никаких проблем там - уже давно не возникает. 2. любой доступ к глобальным переменным - обёрнут критическими секциями. Это уже было сказано Михаилом Булычевым. Строго говоря, такие вопросы Вам - и надо ему задавать, а не "бодаться" с другими.
Вячеслав написал: Да, создаются 2 системных потока, да, в каждом из них выполняется lua_pcall
Всегда, при разговоре на эту тему - разделяйте понятия: системный поток - это НЕ LUA-поток, а поток ОС LUA-поток - это не поток ОС. LUA-поток - сродни корутинам.
В самой LUA - никакие системные потоки - не создаются