Вопрос о целостности данных при работе с таблицей из разных вычислительных потоков

Страницы: 1
RSS
Вопрос о целостности данных при работе с таблицей из разных вычислительных потоков
 
Предположим, что в скрипте заведена таблица t. В потоке коллбэков в функции OnAllTrade() в таблицу t вставляются элементы. Пусть ключом будет цена, а значением -- номер сделки. В потоке main из этой таблицы происходит чтение данных по ключу. Поскольку потоки работают параллельно, то не исключена, например, такая ситуация.

1) В потоке коллбэка в таблицу вставляется новый элемент, что приводит к необходимости перестройки внутренней структуры таблицы t (не знаю, как реализована таблица внутри Lua, но по аналогии c реализацией хэш-таблиц в Java допускаю, что иногда при вставках приходится существенно поменять внутренности объекта).

2) Процесс внутренней перестройки таблицы ещё не закончен, её внутреннее состояние неконсистентно, а в параллельном потоке main() начинается чтение из таблицы t по какому-то ключу.

Вопросы:

а) Есть ли гарантия, что из таблицы будет корректно прочитан элемент (возможно, nil), а не произойдёт какая-то ошибка?

б) А если в п.1 элементы удаляются из таблицы?

Спрашиваю потому, что одно дело обеспечить целостность примитивных данных, другое дело -- объектов со сложной внутренней структурой. Разработчики QLua не зря добавили специальные "Потокобезопасные функции для работы с таблицами Lua". Если добавление/удаление данных из lua-таблиц в самой lua-машине реализовано потокобезопасно, то это положительно отвечает на вопросы а) и б). Если нет -- тогда беда.
 
Да, проблема существует.

http://www.bot4sale.ru/blog-menu/qlua/spisok-statej/455-queue.html

или table.sinsert   table.sremove
www.bot4sale.ru

Пасхалочка для Алексея Иванникова: https://forum.quik.ru/messages/forum10/message63088/topic7052/#message63088
 
Я понимаю, почему функции table.sinsert и table.sremove введены в QLua: надо не только вставить/удалить один элемент, но ещё и сдвинуть остальные элементы таблицы по номерам.

Как написано выше по ссылке, я сам так делаю при передаче данных из коллбэков в main. Вопрос был про одиночное добавление/удаление элементов в таблицу без сдвига всех остальных. По идее, он должен работать, иначе будет рушиться даже этот алгоритм.
 
Действия типа

a={}
a.xxx = 1
a.yyy = 2
a.xxx = nil

и подобные безопасны
www.bot4sale.ru

Пасхалочка для Алексея Иванникова: https://forum.quik.ru/messages/forum10/message63088/topic7052/#message63088
 
Спасибо, можно считать, что тема закрыта.
 
Является ли атомарной операция присвоения одновременно нескольким переменным?
Код
a, b = 1, 2
или
Код
x, y = y, x
 
Цитата
Йцукен написал:
атомарной операция присвоения одновременно нескольким переменным
нет.
При множественном присваивании Lua сначала вычисляет все значения, а затем выполняет присваивание.
 
Цитата
nikolz написал:
При множественном присваивании Lua сначала вычисляет все значения, а затем выполняет присваивание.
А если все значения уже вычислены, как в примерах выше?
 
Цитата
Йцукен написал:
a, b = 1, 2
это выполнится на этапе компиляции.
а это
x, y = y, x
как указано ранее
 
Йцукен,
Интересно, как Вы реализуете обращение к этим переменным в разных потоках?
 
Похоже, операция множественного присвоения выполняется атомарно.
Вот такой код работает без ошибок:
Скрытый текст
 
Цитата
Йцукен написал:
Похоже, операция множественного присвоения выполняется атомарно.
Вот такой код работает без ошибок:
    Скрытый текст        
Код
   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   
и как вы проверили, что выполняется атомарно?  Где доказательство?
 
Атомарная (греч. άτομος — неделимое) операция — операция, которая либо выполняется целиком, либо не выполняется вовсе; операция, которая не может быть частично выполнена и частично не выполнена.
https://ru.wikipedia.org/wiki/Атомарная_операция
 
Цитата
nikolz написал:
как вы проверили, что выполняется атомарно?  Где доказательство?
Вот: #11
Цитата
Йцукен написал:
такой код работает без ошибок

Можете опровергнуть?
 
Цитата
Йцукен написал:
Цитата
nikolz написал:
как вы проверили, что выполняется атомарно?  Где доказательство?
Вот:  #11
Цитата
Йцукен написал:
такой код работает без ошибок

Можете опровергнуть?
Как Вы это установили?  какие ошибки?
---------------------
Вот вам тест:
Код
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
 
Йцукен,
Вы, очевидно, ждете системные ошибки, типа ошибок обращения к памяти. Верно?
Но их может и не быть.
Атомарность обнаруживается например тем, что записав в ячейку 1 и прибавив к ней 1 Вы ожидаете прочитать из нее 2, а читаете 0, так как с момента прибавления 1 до момента чтения результата другое ядро успело записать в эту ячейку ноль.
 
 
тест поправил:
Код
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
 
Цитата
nikolz написал:
Вы, очевидно, ждете системные ошибки, типа ошибок обращения к памяти. Верно?
Не верно. В моём тесте неатомарность проявится, если значение любой из переменных (a1, a2, a3, a4, a5, a6, a7, a8, a9) в любой момент времени будет отличным от остальных. Это будет означать, что поток main стал читать значения переменных в то время, когда в другом потоке произошла запись значений только части из них.

Цитата
nikolz написал:
Вот вам тест:
И? Результаты, выводы?
 
Цитата
Йцукен написал:
В моём тесте неатомарность проявится, если значение любой из переменных (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
 
Цитата
nikolz написал:
x,y=y,x;
       if x~=99 or y~=199 then
           Log:write(tostring(x)..","..tostring(y).."\n")
Даже если вы поймаете тут ошибку, то подумайте вот о чём:
1) Может ли другой поток поменять значения переменных перед if ? Будет ли это означать отсутсвие атомарности в операторе присваивания?
2) Может ли другой поток поменять значения переменных перед выводом их в файл, т.е. гарантированно ли вы получите те значения, которые проверяли в if ?

Скрытый текст
 
Цитата
Йцукен написал:
Цитата
nikolz написал:
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 все просто вылетит аварийно)
 
nikolz, вы же сами дали ссылку на статью:
Цитата
Атомарность операций может обеспечиваться аппаратно (аппаратурой) и программно (программным кодом). В первом случае используются особые машинные инструкции, атомарность выполнения которых гарантируется аппаратурой. Во втором случае используются специальные программные средства синхронизации, с помощью которых осуществляется блокировка разделяемого ресурса; после блокировки выполняется операция, которую требуется выполнить атомарно. Блокировка представляет собой атомарную операцию, которая либо предоставляет ресурс в пользование потоку, либо сообщает потоку о том, что ресурс уже используется другим потоком или процессом (занят).

Теперь если ваш код модифицировать следующим образом:
Код
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  
 
Цитата
nikolz написал:
Поправьте так и ничего не повесится
А слона-то вы и не заметили. Я специально модифицировал скрипт, чтобы показать, какие инструкции выполняются под блокировкой (в используемой версии Lua).
 
Цитата
Йцукен написал:
Цитата
nikolz написал:
Поправьте так и ничего не повесится
А слона-то вы и не заметили. Я специально модифицировал скрипт, чтобы показать, какие инструкции выполняются под блокировкой (в используемой версии Lua).
Если Вам интересно изучить, какие функции выполняются с блокировкой в Lua, то изучайте исходники.
https://www.lua.org/source/5.4/lobject.h.html#hvalue
Там все есть. Я этот этап прошел очень давно. Не вижу особого смысла снова возвращаться к этой теме.  
Страницы: 1
Читают тему
Наверх