Вопрос о целостности данных при работе с таблицей из разных вычислительных потоков
Пользователь
Сообщений: Регистрация: 31.01.2015
22.09.2015 06:03:55
Предположим, что в скрипте заведена таблица t. В потоке коллбэков в функции OnAllTrade() в таблицу t вставляются элементы. Пусть ключом будет цена, а значением -- номер сделки. В потоке main из этой таблицы происходит чтение данных по ключу. Поскольку потоки работают параллельно, то не исключена, например, такая ситуация.
1) В потоке коллбэка в таблицу вставляется новый элемент, что приводит к необходимости перестройки внутренней структуры таблицы t (не знаю, как реализована таблица внутри Lua, но по аналогии c реализацией хэш-таблиц в Java допускаю, что иногда при вставках приходится существенно поменять внутренности объекта).
2) Процесс внутренней перестройки таблицы ещё не закончен, её внутреннее состояние неконсистентно, а в параллельном потоке main() начинается чтение из таблицы t по какому-то ключу.
Вопросы:
а) Есть ли гарантия, что из таблицы будет корректно прочитан элемент (возможно, nil), а не произойдёт какая-то ошибка?
б) А если в п.1 элементы удаляются из таблицы?
Спрашиваю потому, что одно дело обеспечить целостность примитивных данных, другое дело -- объектов со сложной внутренней структурой. Разработчики QLua не зря добавили специальные "Потокобезопасные функции для работы с таблицами Lua". Если добавление/удаление данных из lua-таблиц в самой lua-машине реализовано потокобезопасно, то это положительно отвечает на вопросы а) и б). Если нет -- тогда беда.
Пользователь
Сообщений: Регистрация: 30.01.2015
22.09.2015 09:38:07
Да, проблема существует.
или table.sinsert table.sremove
Пасхалочка для Алексея Иванникова:
Пользователь
Сообщений: Регистрация: 31.01.2015
22.09.2015 09:54:10
Я понимаю, почему функции table.sinsert и table.sremove введены в QLua: надо не только вставить/удалить один элемент, но ещё и сдвинуть остальные элементы таблицы по номерам.
Как написано выше по ссылке, я сам так делаю при передаче данных из коллбэков в main. Вопрос был про одиночное добавление/удаление элементов в таблицу без сдвига всех остальных. По идее, он должен работать, иначе будет рушиться даже этот алгоритм.
Пользователь
Сообщений: Регистрация: 30.01.2015
22.09.2015 09:57:44
Действия типа
a={} a.xxx = 1 a.yyy = 2 a.xxx = nil
и подобные безопасны
Пасхалочка для Алексея Иванникова:
Пользователь
Сообщений: Регистрация: 31.01.2015
22.09.2015 10:12:33
Спасибо, можно считать, что тема закрыта.
Пользователь
Сообщений: Регистрация: 02.01.2026
04.01.2026 21:53:08
Является ли атомарной операция присвоения одновременно нескольким переменным?
Код
a, b = 1, 2
или
Код
x, y = y, x
Пользователь
Сообщений: Регистрация: 30.01.2015
05.01.2026 07:36:03
Цитата
Йцукен написал: атомарной операция присвоения одновременно нескольким переменным
нет. При множественном присваивании Lua сначала вычисляет все значения, а затем выполняет присваивание.
Пользователь
Сообщений: Регистрация: 02.01.2026
05.01.2026 09:52:58
Цитата
nikolz написал: При множественном присваивании Lua сначала вычисляет все значения, а затем выполняет присваивание.
А если все значения уже вычислены, как в примерах выше?
это выполнится на этапе компиляции. а это x, y = y, x как указано ранее
Пользователь
Сообщений: Регистрация: 30.01.2015
05.01.2026 10:07:07
, Интересно, как Вы реализуете обращение к этим переменным в разных потоках?
Пользователь
Сообщений: Регистрация: 02.01.2026
06.01.2026 09:14:37
Похоже, операция множественного присвоения выполняется атомарно. Вот такой код работает без ошибок:
Скрытый текст
Код
local run = true
local a1, a2, a3, a4, a5, a6, a7, a8, a9 = 0, 0, 0, 0, 0, 0, 0, 0, 0
local n = 100
function OnAllTrade()
for i = 1, n do
a1, a2, a3, a4, a5, a6, a7, a8, a9 = i, i, i, i, i, i, i, i, i
end
end
function OnParam()
for i = 1, n do
a1, a2, a3, a4, a5, a6, a7, a8, a9 = i, i, i, i, i, i, i, i, i
end
end
function main()
while run do
sleep(1)
local t = prec_time()
for i = 1, n do
if a1 ~= a2 or a1 ~= a3 or a1 ~= a4 or a1 ~= a5 or a1 ~= a6 or a1 ~= a7 or a1 ~= a8 or a1 ~= a9 or a1 == nil then
error("error")
end
end
end
end
function OnStop()
run = nil
end
Пользователь
Сообщений: Регистрация: 30.01.2015
06.01.2026 12:49:03
Цитата
Йцукен написал: Похоже, операция множественного присвоения выполняется атомарно. Вот такой код работает без ошибок: Скрытый текст
Код
local run = true
local a1, a2, a3, a4, a5, a6, a7, a8, a9 = 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0
local n = 100
function OnAllTrade ()
for i = 1 , n do
a1, a2, a3, a4, a5, a6, a7, a8, a9 = i, i, i, i, i, i, i, i, i
end
end
function OnParam ()
for i = 1 , n do
a1, a2, a3, a4, a5, a6, a7, a8, a9 = i, i, i, i, i, i, i, i, i
end
end
function main ()
while run do
sleep ( 1 )
local t = prec_time()
for i = 1 , n do
if a1 ~ = a2 or a1 ~ = a3 or a1 ~ = a4 or a1 ~ = a5 or a1 ~ = a6 or a1 ~ = a7 or a1 ~ = a8 or a1 ~ = a9 or a1 = = nil then
error( "error" )
end
end
end
end
function OnStop ()
run = nil
end
и как вы проверили, что выполняется атомарно? Где доказательство?
Пользователь
Сообщений: Регистрация: 30.01.2015
06.01.2026 12:51:38
Атомарная ( άτομος — неделимое) операция — операция, которая либо выполняется целиком, либо не выполняется вовсе; операция, которая не может быть частично выполнена и частично не выполнена.
Пользователь
Сообщений: Регистрация: 02.01.2026
06.01.2026 13:01:50
Цитата
nikolz написал: как вы проверили, что выполняется атомарно? Где доказательство?
написал: как вы проверили, что выполняется атомарно? Где доказательство?
Вот:
Цитата
написал: такой код работает без ошибок
Можете опровергнуть?
Как Вы это установили? какие ошибки? --------------------- Вот вам тест:
Код
minfo=debug.getinfo(1, "S").source:sub(2); path=minfo:match("(.*[/\\])") or "."
Log=io.open(path.."/test.log","w");
fconnect=1;
local x,y;
function main()
while fconnect do
fconnect=2
while fconnect==2 do
y=99 x=199 x,y=y,x;
if x~=99 or y~=199 then
Log:write(tostring(x)..","..tostring(y).."\n");Log:flush();
end
end
sleep(10);
end
sleep(1000)
end
function OnParam(c, s)
y=100 x=200 x,y=y,x;
if x~=100 or y~=200 then
Log:write(tostring(x)..","..tostring(y).."\n");Log:flush();
end
end
function OnInit(p)
fconnect=1;
end
Пользователь
Сообщений: Регистрация: 30.01.2015
06.01.2026 14:29:22
, Вы, очевидно, ждете системные ошибки, типа ошибок обращения к памяти. Верно? Но их может и не быть. Атомарность обнаруживается например тем, что записав в ячейку 1 и прибавив к ней 1 Вы ожидаете прочитать из нее 2, а читаете 0, так как с момента прибавления 1 до момента чтения результата другое ядро успело записать в эту ячейку ноль.
Пользователь
Сообщений: Регистрация: 30.01.2015
06.01.2026 14:39:21
тест поправил:
Код
minfo=debug.getinfo(1, "S").source:sub(2); path=minfo:match("(.*[/\\])") or "."
Log=io.open(path.."/test.log","w");
fconnect=1;
local x,y;
function main()
while true do
y=99 x=199 x,y=y,x;
if x~=99 or y~=199 then
Log:write(tostring(x)..","..tostring(y).."\n");Log:flush();
end
sleep(1);
end
end
function OnParam(c, s)
y=100 x=200 x,y=y,x;
if x~=100 or y~=200 then
Log:write(tostring(x)..","..tostring(y).."\n");Log:flush();
end
end
function OnInit(p)
fconnect=1;
end
Пользователь
Сообщений: Регистрация: 02.01.2026
06.01.2026 15:08:05
Цитата
nikolz написал: Вы, очевидно, ждете системные ошибки, типа ошибок обращения к памяти. Верно?
Не верно. В моём тесте неатомарность проявится, если значение любой из переменных (a1, a2, a3, a4, a5, a6, a7, a8, a9) в любой момент времени будет отличным от остальных. Это будет означать, что поток main стал читать значения переменных в то время, когда в другом потоке произошла запись значений только части из них.
Йцукен написал: В моём тесте неатомарность проявится, если значение любой из переменных (a1, a2, a3, a4, a5, a6, a7, a8, a9) в любой момент времени будет отличным от остальных. Это будет означать, что поток main стал читать значения переменных в то время, когда в другом потоке произошла запись значений только части из них.
Другими словами, если мы попадём внутрь блока
Код
if a1 ~= a2 or a1 ~= a3 or a1 ~= a4 or a1 ~= a5 or a1 ~= a6 or a1 ~= a7 or a1 ~= a8 or a1 ~= a9 or a1 == nil then
error("error")
end
Пользователь
Сообщений: Регистрация: 02.01.2026
06.01.2026 15:19:37
Цитата
nikolz написал: x,y=y,x; if x~=99 or y~=199 then Log:write(tostring(x)..","..tostring(y).."\n")
Даже если вы поймаете тут ошибку, то подумайте вот о чём: 1) Может ли другой поток поменять значения переменных перед if ? Будет ли это означать отсутсвие атомарности в операторе присваивания? 2) Может ли другой поток поменять значения переменных перед выводом их в файл, т.е. гарантированно ли вы получите те значения, которые проверяли в if ?
написал: x,y=y,x; if x~=99 or y~=199 then Log:write(tostring(x)..","..tostring(y).."\n")
Даже если вы поймаете тут ошибку, то подумайте вот о чём: 1) Может ли другой поток поменять значения переменных перед if ? Будет ли это означать отсутсвие атомарности в операторе присваивания? 2) Может ли другой поток поменять значения переменных перед выводом их в файл, т.е. гарантированно ли вы получите те значения, которые проверяли в if ?
Скрытый текст Ответ на 2-й вопрос - Нет.
Не знаю что Вы называете атомарностью операции. Но В языке программирования Lua нет специальных атомарных операций. ------------------------------------- Дело в том, что скрипт луа выполняется на виртуальной машине, а его операторы преобразуются в байт код. Т е любой оператор -это набор функций на СИ. Поэтому официально в луа нет атомарных операций. Можно говорить о синхронизации потоков при обращении к данным. Что делается известными методами, в том числе и в VMLua и в библиотеке QLUA. ----------------- Кроме того, VMLua изначально сделана не многопоточная( это всем известно). Поэтому , если механизм синхронизации встроен, то ответ на ваш 2 вопрос будет нет, а если он не встроен , то ответ Да. Рекомендую открыть в КВИКе еще поток ОС с VMLua как корутину (так открыт поток main) и обратится из него к общим переменным. И Вы увидите ответ на Ваш вопрос. ( У Вас QUIK все просто вылетит аварийно)
Атомарность операций может обеспечиваться аппаратно (аппаратурой) и программно (программным кодом). В первом случае используются особые машинные инструкции, атомарность выполнения которых гарантируется аппаратурой. Во втором случае используются специальные программные средства синхронизации, с помощью которых осуществляется блокировка разделяемого ресурса; после блокировки выполняется операция, которую требуется выполнить атомарно. Блокировка представляет собой атомарную операцию, которая либо предоставляет ресурс в пользование потоку, либо сообщает потоку о том, что ресурс уже используется другим потоком или процессом (занят).
Теперь если ваш код модифицировать следующим образом:
Код
local x, y
function main()
while true do
y = 99
x = 199
x, y = y, x
if x ~= 99 or y ~= 199 then
s = tostring(x)..","..tostring(y).."\n"
end
end
end
и запустить, то QUIK повесится (версия Lua: 5.4.1).
Если же конкатенацию строк с си-функцией tostring вынести за пределы блока if
Код
local x, y
function main()
while true do
y = 99
x = 199
x, y = y, x
if x ~= 99 or y ~= 199 then end
s = tostring(x)..","..tostring(y).."\n"
end
end
то QUIK, хоть и загрузит одно ядро на 100%, но остаётся доступным и позволяет остановить скрипт. Что, как бы, намекает, что инструкции
Код
y = 99
x = 199
x, y = y, x
if x ~= 99 or y ~= 199 then
выполняются под блокировкой. А вот преобразование числа в строку - нет. Отсюда и ответ на 2-й : вы не сможете таким образом гарантированно вывести в файл значения переменных x и y, которые были в момент проверки их оператором if, поскольку другой поток может подменить их значения в этот момент.
Теперь если ваш код модифицировать следующим образом:
Код
local x, y
function main ()
while true do
y = 99
x = 199
x, y = y, x
if x ~ = 99 or y ~ = 199 then
s = tostring(x) .. "," .. tostring(y) .. "\n"
end
end
end
и запустить, то QUIK повесится (версия Lua: 5.4.1).
Поправьте так и ничего не повесится:
Код
local x, y
function main ()
while true do
y=99
x= 99
x, y = y, x
if x~= 99 or y ~= 199 then
s = tostring(x) .. "," .. tostring(y) .. "\n"
end
sleep(1);
end
end
Пользователь
Сообщений: Регистрация: 02.01.2026
07.01.2026 09:35:34
Цитата
nikolz написал: Поправьте так и ничего не повесится
А слона-то вы и не заметили. Я специально модифицировал скрипт, чтобы показать, какие инструкции выполняются под блокировкой (в используемой версии Lua).
А слона-то вы и не заметили. Я специально модифицировал скрипт, чтобы показать, какие инструкции выполняются под блокировкой (в используемой версии Lua).
Если Вам интересно изучить, какие функции выполняются с блокировкой в Lua, то изучайте исходники.
Там все есть. Я этот этап прошел очень давно. Не вижу особого смысла снова возвращаться к этой теме.