std::recursive_mutex и cинхронизация потоков в Lua
Пользователь
Сообщений: Регистрация: 27.01.2016
27.02.2016 14:54:48
Есть код, который выполняется в main и в callback'ах Lua. Задача - обеспечить доступ к разделяемому ресурсу. Допустим, доступ к разделяемому ресурсу осуществляется с помощью C++ или .
Например, пусть один поток добавляет элемент в таблицу, а другой читает её. Сам вопрос в коде.
Код
-- положим, есть внешняя библиотека, которая создаёт 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.
Пользователь
Сообщений: Регистрация: 30.01.2015
27.02.2016 17:56:28
для начала можно почитать документацию QLUA где указано: ---------------------------------- Потокобезопасные функции для работы с таблицами Lua Одновременная работа с таблицами из функций обратного вызова скрипта и функции main() может приводить к неопределенным ситуациям. Для решения этой проблемы qlua.dll предоставляет потокобезопасные аналоги стандартных функций Lua. Формат вызова потокобезопасной функции совпадает с форматом вызова аналогичной стандартной функции Lua. В таблице представлены стандартные функции Lua и соответствующие им потокобезопасные аналоги:
Стандартная функция Lua
Потокобезопасная функция
concat
sconcat
remove
sremove
insert
sinsert
sort
ssort
Этого достаточно?
Пользователь
Сообщений: Регистрация: 30.01.2015
27.02.2016 18:00:34
Потокобезопасные функции для работы с таблицами Lua
Одновременная работа с таблицами из функций обратного вызова скрипта и функции main() может приводить к неопределенным ситуациям. Для решения этой проблемы qlua.dll предоставляет потокобезопасные аналоги стандартных функций Lua. Формат вызова потокобезопасной функции совпадает с форматом вызова аналогичной стандартной функции Lua. вместо concat ->sconcat remove ->sremove insert->sinsert sort->ssort
Пользователь
Сообщений: Регистрация: 27.01.2016
27.02.2016 18:22:56
Николай Камынин, вопрос немного не об этом. Я уже выполняю синхронизацию на уровне мьютексов. Вопрос в том, прочитаются ли данные в потоке main сразу после вставки их в таблицу в обработчике callback'а или наоборот.
Перечисленных потокобезопасных функций мне не достаточно. В частности, мне нужно пройтись по таблице, в которой добавляются/удаляются элементы из callback'а и выполнить определённые действия для некоторых элементов по условию.
Пользователь
Сообщений: Регистрация: 27.01.2016
27.02.2016 18:26:03
@тех.поддержка: При необходимости, для тестов, могу выслать код библиотеки, которая обслуживает userdata для критических секций. Вопрос по большей части технический. Почти уверен, что так как я делаю, делать можно. Хотелось бы услышать подтверждение от команды Quik'а.
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
27.02.2016 21:55:06
Вячеслав, то как вы делаете, на мой взгляд в общем-то идеально. Не могу только понять при чем тут версия квика, от неё точно ничего здесь не зависит.
Идеальность в том, что вы, с одной стороны, полностью блокируете работу с таблицей из другого потока на все время добавления в неё элемента, с другой стороны полностью блокируете добавление в таблицу на всё время перебора имеющихся элементов внутри main(). Т.е. у вас всегда гарантированно в цикле обрабатывается некое полностью статичное состояние таблицы. И это само по себе безусловно здорово.
Однако, есть важный момент. Состоит он в том, что на всё время обработки таблицы (в main()) у вас фактически полностью блокирована работа call-back'ов при наступлении события OnParam(). И тут уже возникает вопрос про эффективность такого алгоритма. В самом деле: видно, что вы пытаетесь сделать "очередь событий" в таблице, и события из этой очереди разгребаете как бы в отдельном потоке main(). Но в вашем алгоритме вы не можете положить в таблицу очередное событие из OnParam() до тех пор, пока не обработали все имеющиеся события в таблице. Т.е. у вас получится, что в таблицу попали какие-то события (в виде элементов таблицы), вы их в main() обрабатываете - и на всё время этой обработки у вас OnParam() тупо залочен, QUIK стоит и ждёт, пока вы в main() обработаете все имеющиеся элементы таблицы. Не видно, чтобы тут случился выигрыш-то от двухпоточности. У вас всё равно каждый поток постоянно ждёт другой поток, в том числе и поток добавления событий.
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
27.02.2016 21:56:38
Цитата
Вячеслав написал: Вопрос в том, прочитаются ли данные в потоке main сразу после вставки их в таблицу в обработчике callback'а
Да, прочитаются. Ну с поправкой на sleep(50), конечно, т.е. "сразу" может случиться через 50 мс, после фактического добавления.
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
27.02.2016 21:58:42
Что касается "Потокобезопасных функции для работы с таблицами Lua" - то они несколько про другое. Во всяком случае они не реализуют тот алгоритм, который приведён в примере. Отличие будет в том, что не будет лочения потоков на время обработки всех элементов таблицы. Подходит ли это автору вопроса - не понятно. Он ведь лочит вокруг всего цикла обработки, а не вокруг обработки одного элемента.
Пользователь
Сообщений: Регистрация: 30.01.2015
28.02.2016 09:48:50
Цитата
Вячеслав написал: Николай Камынин , вопрос немного не об этом. Я уже выполняю синхронизацию на уровне мьютексов. Вопрос в том, прочитаются ли данные в потоке main сразу после вставки их в таблицу в обработчике callback'а или наоборот.
Перечисленных потокобезопасных функций мне не достаточно. В частности, мне нужно пройтись по таблице, в которой добавляются/удаляются элементы из callback'а и выполнить определённые действия для некоторых элементов по условию.
Я делаю иначе: если потоку main делать нечего, то я его усыпляю. в результате он не занимает никаких ресурсов процессора. когда я обновляю таблицу, то пинаю поток main, он просыпается и обрабатывает то, что ему пришло в очереди. после этого он снова засыпает до нового пинка.
Пользователь
Сообщений: Регистрация: 30.01.2015
28.02.2016 09:56:44
что-же касается простаивания колбеков, при работе main, то это можно решить путем копирования очериди в локальный массив в main. и далее main работает с копией, а колбеки с очередью
Пользователь
Сообщений: Регистрация: 30.01.2015
28.02.2016 10:05:37
и еще можно обойтись без мьютекса вообще лишь используя concat ->sconcat remove ->sremove insert->sinsert sort->ssort и создавая копию очереди в main.
Пользователь
Сообщений: Регистрация: 30.01.2015
28.02.2016 10:11:25
вопрос является ли потокобезопасным оператор извлечения элемента таблицы t: local x=t[1];
Пользователь
Сообщений: Регистрация: 30.01.2015
28.02.2016 10:19:31
т е на какой момент извлеченный в main элемент будет первым в таблице, если колбеке есть вставка первого элемента?
Пользователь
Сообщений: Регистрация: 27.01.2016
28.02.2016 12:40:10
Цитата
swerg написал: Вячеслав , то как вы делаете, на мой взгляд в общем-то идеально. Не могу только понять при чем тут версия квика, от неё точно ничего здесь не зависит.
Идеальность в том, что вы, с одной стороны, полностью блокируете работу с таблицей из другого потока на все время добавления в неё элемента, с другой стороны полностью блокируете добавление в таблицу на всё время перебора имеющихся элементов внутри main(). Т.е. у вас всегда гарантированно в цикле обрабатывается некое полностью статичное состояние таблицы. И это само по себе безусловно здорово.
Однако, есть важный момент. Состоит он в том, что на всё время обработки таблицы (в main()) у вас фактически полностью блокирована работа call-back'ов при наступлении события OnParam(). И тут уже возникает вопрос про эффективность такого алгоритма. В самом деле: видно, что вы пытаетесь сделать "очередь событий" в таблице, и события из этой очереди разгребаете как бы в отдельном потоке main(). Но в вашем алгоритме вы не можете положить в таблицу очередное событие из OnParam() до тех пор, пока не обработали все имеющиеся события в таблице. Т.е. у вас получится, что в таблицу попали какие-то события (в виде элементов таблицы), вы их в main() обрабатываете - и на всё время этой обработки у вас OnParam() тупо залочен, QUIK стоит и ждёт, пока вы в main() обработаете все имеющиеся элементы таблицы. Не видно, чтобы тут случился выигрыш-то от двухпоточности. У вас всё равно каждый поток постоянно ждёт другой поток, в том числе и поток добавления событий.
1. Если бы всё было идеально, я бы не спрашивал ответа на форуме. Загвоздка в том, что обычная Lua 5.1 поддерживает работу только в одном потоке, поэтому Quik использует внутри либо одну из (очень похоже на Lua Lanes), либо придерживается своей реализации. В любом случае, проблема в том, что в функции callback'а и функции main используется разные структуры lua_State*, и сразу ли значение, присвоенное в одном lua_State станет доступно в другом - вопрос к команде Quik. Интересно, что адреса всех глобальных таблиц (получаются при вызове tostring(tbl)) в функции main и callback'ах совпадают.
2. В main у меня проход по таблице, если условие для элемента выполнилось, то производится дополнительная работа, но самый максимум получается 20 мс при блокированной таблице (и то раз в 5 секунд), поэтому некритично, если на это время заблокируется главный поток Quik, а так в main я ещё выставляю транзакции (без блокирования чего-либо).
Пользователь
Сообщений: Регистрация: 30.01.2015
Роботорговец
28.02.2016 13:35:03
Цитата
Вячеслав написал: 2. В main у меня проход по таблице, если условие для элемента выполнилось, то производится дополнительная работа, но самый максимум получается 20 мс при блокированной таблице (и то раз в 5 секунд), поэтому некритично, если на это время заблокируется главный поток Quik, а так в main я ещё выставляю транзакции (без блокирования чего-либо).
Позвольте поинтересоваться, зачем переносить обработку в другой поток, если во время обработки основной поток всё равно простаивает? Какая цель преследуется?
Надо делать так, как надо. А как не надо - делать не надо.
Пользователь
Сообщений: Регистрация: 27.01.2016
28.02.2016 14:49:05
Цитата
Старатель написал: Позвольте поинтересоваться, зачем переносить обработку в другой поток, если во время обработки основной поток всё равно простаивает? Какая цель преследуется?
К сожалению, не понял вопроса. Схематично у меня 2 потока (callback'и и main) выглядят так. Своих отдельных потоков не создаю.
Код
callback {
найти (связать со сделкой) экземпляр ордера
в случае, если весь объйм транзакции выбран полностью, отписаться в лог и удалить из списка активных транзакций
если выбранный объём изменился или произошла ошибка, отписаться в лог
}
main {
while(running) do
по алгоритму выставить нужные транзакции
по алгоритму выполнять подписку на стаканы нужных инструментов с помощью Subscribe_Level_II_Quotes
раз в 5 секунд {
блокировать список активных транзакций
проверять не истёк ли для них таймаут ожидания ответа (для связки ordernum -- trans_id)
те, для которых истёк, удалять из списка с записью в лог
}
end
}
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
28.02.2016 20:30:52
Цитата
Николай Камынин написал: т е на какой момент извлеченный в 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 уже успел и новый элемент в таблицу добавиться, и значение ему присвоиться.
Собственно классика многопоточности )
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
28.02.2016 20:35:11
Цитата
Вячеслав написал: используется разные структуры 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.
Пользователь
Сообщений: Регистрация: 27.01.2016
28.02.2016 22:59:05
Цитата
Вячеслав написал: удостоверился в этом, выведя на печать таблицу LUA_REGISTRYINDEX. LUA_REGISTRYINDEX для потока main хранит объект thread. При этом для главного потока LUA_REGISTRYINDEX в момент инициализации пустая.
Ошибся. LUA_REGISTRYINDEX для state'ов созданных с помощью lua_newthread совпадает с глобальным. Т.о. поток lua_newthread создаётся после интерпретации всего скрипта.
Пользователь
Сообщений: Регистрация: 27.01.2016
28.02.2016 23:06:58
Вопрос к техподдержке:
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'ов).
Пользователь
Сообщений: Регистрация: 27.01.2016
28.02.2016 23:17:28
Подозреваю, что используется всё же 1 мьютекс, если даже Roberto Ierusalimschy об этом заявляет.
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
29.02.2016 06:20:58
Цитата
Вячеслав написал: P.S. Подозреваю, что в QLua просто переопределили lua_lock и lua_unlock, чтобы разрешить многопоточность. Сейчас выясняю, как выполняется синхронизания глобальных переменных в Lua между потоками при переопределённых lua_lock / lua_unlock.
Вы так пишете, как-будто есть другие способы. Про это уже писалось (на старом форуме, как водится).
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
29.02.2016 06:24:10
Цитата
Вячеслав написал: QLua использует 1 мьютекс для разделения выполнения функции main и callback'ов или 2?
Вы задаёте просто удивительные вопросы, ну вот честно. Ради любопытства, можете сказать, как вы предполагаете использовать 2 мьютекса для синхронизации между потоками? ну вот чисто теоретически? А если это никак - то о чем вопрос??
Пользователь
Сообщений: Регистрация: 30.01.2015
29.02.2016 07:06:20
У меня вопрос к автору темы. А зачем использовать мьютекс, если у нас один процесс? Спасибо
Вячеслав написал: QLua использует 1 мьютекс для разделения выполнения функции main и callback'ов или 2?
Вы задаёте просто удивительные вопросы, ну вот честно. Ради любопытства, можете сказать, как вы предполагаете использовать 2 мьютекса для синхронизации между потоками? ну вот чисто теоретически? А если это никак - то о чем вопрос??
Понадобится более двух. По одному на доступ к VM каждого из потоков, а также мьютексы для доступа к переменным LUA_REGISTRYINDEX и LUA_GLOBALSINDEX, + по одному мьютексу на каждую таблицу и userdata. Да, возможно было бы глупо с моей стороны считать, что в Quik'е решились на подобный рефакторинг движка Lua.
Пользователь
Сообщений: Регистрация: 27.01.2016
01.03.2016 23:21:15
Цитата
swerg написал: Вы так пишете, как-будто есть другие способы. Про это уже писалось (на старом форуме, как водится).
Меня не было на старом форуме, поэтому не знаю.
Пользователь
Сообщений: Регистрация: 27.01.2016
01.03.2016 23:24:54
Цитата
Николай Камынин написал: У меня вопрос к автору темы. А зачем использовать мьютекс, если у нас один процесс? Спасибо
Даже не знаю с чего начать. В Windows "процесс" - это контейнер, в котором выполняются потоки (нити). Потоки имеют состояние выполнения, которое включает регистры процессора и стек. Адресное пространство (память) у потоков одного процесса общая. Для синхронизации доступа к ресурсам, которые используются несколькими потоками используют мьютексы.
Пользователь
Сообщений: Регистрация: 27.01.2016
01.03.2016 23:29:31
swerg, Подытожу: в Lua переключение потоков (глобального на main и обратно) происходит только в момент вызова любой Си функции (любой библиотечной функции). В остальное время, если Lua-код выполняется, то он выполняется атомарно. Поэтому же Quik можно подвесить простым кодом
Код
while (true) do end
Поставив его, например, в main и пусть в этот момент Quik захочет вызвать какой-нибудь callback. В результате, Quik зависнит на lua_lock Си функции, т.к. Lua VM будет в этот момент занята потоком main, выполняющим наш бесконечный цикл.
Я прав?
Пользователь
Сообщений: Регистрация: 23.01.2015
01.03.2016 23:39:04
Цитата
Вячеслав написал: Поэтому же 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 "процесс" - это контейнер, в котором выполняются потоки (нити). Потоки имеют состояние выполнения, которое включает регистры процессора и стек. Адресное пространство (память) у потоков одного процесса общая. Для синхронизации доступа к ресурсам, которые используются несколькими потоками используют мьютексы.
Я собственно спросил зачем мьютекс если один процесс, в том плане, что в одном процессе синхронизацию потоков эффективнее делать другими средствами либо в пользовательском режиме -атомарным операциями и критическими секциями либо ядерными - событиями. Это быстрее , чем мьютекс.
Я не понял контекст вашего оригинального сообщения. Есть просто термин , есть (или - по сути реализованые через аналог критических секций на уровне Concurrency namespace'а в MS CRT), и есть , который может использоваться несколькими процессами сразу. В первом сообщении я сразу упомянул, что имею ввиду std::recursive_mutex или критические секции, а дальше уже оба варианта называл просто "мьютексом", чтобы не писать одно и то же по нескольку раз.
Я не понял контекст вашего оригинального сообщения. Есть просто термин , есть (или - по сути реализованые через аналог критических секций на уровне Concurrency namespace'а в MS CRT), и есть , который может использоваться несколькими процессами сразу. В первом сообщении я сразу упомянул, что имею ввиду 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).
Пользователь
Сообщений: Регистрация: 01.02.2015
09.03.2016 17:14:05
макросы lua_lock и lua_unlock - уже давно реализованы через критические секции. Так советовалось ещё на страницах "lua-users wiki", а также не раз подтверждалось самими разработчиками. Использование мьютексов конкретно в данном контексте - т.е. как замена критическим секциям в вышеуказанных макросах - глупая идея. Что - тоже не раз уже упоминалось.
Более того, на страницах форума уже было расписано во всех деталях все тонкости реализации многопоточности в квике - пользуйтесь гуглом и обрящите.
Пользователь
Сообщений: Регистрация: 01.02.2015
09.03.2016 17:18:48
Цитата
Вячеслав написал: В таком случае, лишь одна команда Lua VM выполняется атомарно
забавно слушать рассуждения об атомарности в данном контексте..))
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
10.03.2016 06:18:03
Цитата
Вячеслав написал: Предполагаю, что QLua использует отладочные функции debug.sethook для перехвата управления и переключения Lua VM c потока main на поток callback'ов.
В QLua используются реальные разные потоки операционной системы Windows. Потоки переключает Windows, где реализована вытесняющая мультизадачность. Бесконечные циклы не мешают Windows переключать процессор между исполнением кода разных потоков, никакая Lua VM тут ни при чем. Что вы еще пытаетесь найти в Lua VM?
И да, код в смысле Lua-программы прерывается в реализованном случае вовсе не обязательно "между операторами Lua". Прерваться код может и "посередине выполнения одного из операторов".
Пользователь
Сообщений: Регистрация: 27.01.2016
10.03.2016 13:34:38
тот самый, Положим, я называю критические секции . В чём заключается Ваше предложение?
Пользователь
Сообщений: Регистрация: 27.01.2016
10.03.2016 13:42:41
Цитата
swerg написал: И да, код в смысле Lua-программы прерывается в реализованном случае вовсе не обязательно "между операторами Lua". Прерваться код может и "посередине выполнения одного из операторов".
Да, выполнение любого потока может прерваться в любом месте. Что я имею ввиду, так это то, что 2 Lua-команды (из потока main и потока callback'а) одновременно выполняться не могут: если выполнение потока main будет приостановлено системным планировщиком на середине выполнения Lua-команды (luaV_execute) и в этот момент начнёт выполняться основной поток Quik, который захочет вызвать наш callback, то основной поток Quik будет ждать на lua_lock операции (планировщик переключит выполнение на другой поток вместо основного потока Quik), т. к. поток main в данный момент владеет мьютексом, который предотвращает одновременное выполнение двух Lua-комманд из разных потоков.
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
10.03.2016 21:19:17
Вячеслав, у вас есть пруф подтверждающий то, что вы сейчас написали, или это ваши фантазии? Как-то всё очень сомнительно выглядит, особенно вот это
Цитата
Вячеслав написал: и в этот момент начнёт выполняться основной поток Quik, который захочет вызвать наш callback, то основной поток Quik будет ждать на lua_lock операции
Пользователь
Сообщений: Регистрация: 02.02.2015
миру мир!
10.03.2016 21:31:23
Собственно исходники ведь доступны. Где в коде luaV_execute есть lua_lock? покажите
И совсем другое дело функции доступа к "переменным", например:
Зачем вы всё время фантазируете, ничего не проверяя, вот что не понятно.
Пользователь
Сообщений: Регистрация: 27.01.2016
11.03.2016 00:21:19
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_lock, затем по цепочке вызовов lua_pcall -> -> -> (этот макрос вызывает на самом деле - см. параметр ф-ции luaD_rawrunprotected) -> -> мы наконец попадаем в (в случае, если наша функция - это Lua-функция). Замечу, что всё это время мы удерживаем критическую секцию (lua_lock). Освободить критическую секцию, пока выполяется luaV_execute мы можем по трём причинам (см. код ): 1. использует luai_threadyield, который позволяет переключиться на выполнение другого потока, 2. в случае вызова Си-функции из Lua-функции (например Sleep), 3. выполняется на каждой итерации LuaVM и, в случае если установлен debug.sethook, выполнит lua_unlock / lua_lock.
Также замечу, что LuaVM (luaV_execute) не использует LuaAPI (функции из ) для доступа к переменным. 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-код, который не приведёт к вызову .
Пользователь
Сообщений: Регистрация: 27.01.2016
11.03.2016 00:26:44
Цитата
которые как раз и вызывают функции из lapi.c
имеется ввиду, что функции из lapi.c вызывают внутренние функции в библиотеке Lua, и их же вызывает luaV_execute напрямую. Например, вызывает , которую напрямую (через макрос) вызывает luaV_execute.
имеется ввиду, что функции из lapi.c вызывают внутренние функции в библиотеке Lua, и их же вызывает luaV_execute напрямую. Например, вызывает , которую напрямую (через макрос) вызывает luaV_execute.
Причем здесь вызов функций? Синхронизировать надо обращение к данным, а не к коду функций. Вы что-то путаете.
Пользователь
Сообщений: Регистрация: 30.01.2015
11.03.2016 06:48:11
причем лишь запись данных.
Пользователь
Сообщений: Регистрация: 30.01.2015
11.03.2016 06:51:42
все решается с помощью одного event, функций sinsert и sremove и использованием глобальных переменных.
Пользователь
Сообщений: Регистрация: 01.02.2015
11.03.2016 10:21:20
Цитата
Вячеслав написал: если выполнение потока main будет приостановлено системным планировщиком на середине выполнения Lua-команды (luaV_execute) и в этот момент начнёт выполняться основной поток Quik, который захочет вызвать наш callback, то основной поток Quik будет ждать на lua_lock операции (планировщик переключит выполнение на другой поток вместо основного потока Quik), т. к. поток main в данный момент владеет мьютексом, который предотвращает одновременное выполнение двух Lua-комманд из разных потоков.
1. по понятным причинам, не ручаюсь на 100% но, код работы с коллбеками, завязан на отдельный LUA-поток в контексте одной виртуальной машины. Другой поток (и физический и LUA-поток) - это, ещё один коллбек - функция "main". Таким образом, никаких проблем там - уже давно не возникает. 2. любой доступ к глобальным переменным - обёрнут критическими секциями. Это уже было сказано Михаилом Булычевым. Строго говоря, такие вопросы Вам - и надо ему задавать, а не "бодаться" с другими.
Пользователь
Сообщений: Регистрация: 01.02.2015
11.03.2016 10:28:05
Цитата
Вячеслав написал: Да, создаются 2 системных потока, да, в каждом из них выполняется lua_pcall
Всегда, при разговоре на эту тему - разделяйте понятия: системный поток - это НЕ LUA-поток, а поток ОС LUA-поток - это не поток ОС. LUA-поток - сродни корутинам.
В самой LUA - никакие системные потоки - не создаются