Re[9]: у него много обращений на чтение, и очень очень редко
От: · Великобритания  
Дата: 09.08.18 16:40
Оценка:
Здравствуйте, VVV, Вы писали:

VVV>·>Не выйдет. size() тоже вообще-то надо внутрь lock засовывать, что делает всю затею бессмысленной.

VVV>·>Можно вместо size() использовать atomic int или что-то подобное, но тоже непригодно для low latency, т.к. doSome может ВНЕЗАПНО лочиться.
VVV>size() имеется в виду функция типа size_t size() { return m_size; }
Т.е. завязываешься на конкретную реализацию конкретного контейнера...

VVV>, в данном случае lock вызывать не надо, нам совсем не важно правильная ли сумма вернётся, важно только что сумма больше 0. Поэтому тут lock не нужен. И да — это был псевдокод — можно вместо size() завести булевскую переменную.

... но всё равно без разницы. Чтение переменной из одного потока, когда она меняется из другого просто так, без всяких многопоточных штуковин — вещь как повезёт. Например, значение 0 может закешироваться в регистре одного потока и из памяти никогда не читаться и изменения в памяти другим потоком не будут видны. Как мининмум нужен membar.
Приведённый ранее код — ошибочен. Если ты так делаешь в реальном коде — обязательно исправь, это бага.

VVV>Про ВНЕЗАПНО лочиться — при многопоточном доступе к данным такое случается. В предложенном мной подходе lock будет вызываться только в случае реального добавления/удаления данных.

Это зависит от требований. В low latecny code такое не должно случаться. Поэтому там нужны специальные lock-free подходы.

VVV>Ещё алгоритм придумался: использовать кольцевой буфер новых/удаляемых объектов. insert/erase двигают tail, doSome двигает head.

Да, такое вроде сработает, но только если doSome выполняется из максимум одного потока.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[10]: у него много обращений на чтение, и очень очень редко
От: VVV Россия  
Дата: 09.08.18 17:36
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, VVV, Вы писали:


VVV>>size() имеется в виду функция типа size_t size() { return m_size; }

·>Т.е. завязываешься на конкретную реализацию конкретного контейнера...

Товарищ! Это _ПСЕВДОКОД_, иллюстрирующий идею. Нет тут никаких конкретных реализаций. Это может быть size(), isChanged(), hasNewItems() и т.д. — это просто ИДЕЯ!

·>... но всё равно без разницы. Чтение переменной из одного потока, когда она меняется из другого просто так, без всяких многопоточных штуковин — вещь как повезёт. Например, значение 0 может закешироваться в регистре одного потока и из памяти никогда не читаться и изменения в памяти другим потоком не будут видны. Как мининмум нужен membar.

·>Приведённый ранее код — ошибочен. Если ты так делаешь в реальном коде — обязательно исправь, это бага.

Есть волшебное слово volatile — оно спасёт. И не будь столь категоричным. Приведи пример, КАК такой код может сработать неправильно?

VVV>>Ещё алгоритм придумался: использовать кольцевой буфер новых/удаляемых объектов. insert/erase двигают tail, doSome двигает head.

·>Да, такое вроде сработает, но только если doSome выполняется из максимум одного потока.

ну это ТС пусть смотрит, подходит ему или нет. Судя по этому куску кода — это похоже на код игры, где doSome — в UI потоке, а insert/erase в потоке gameEngine.
Re[11]: у него много обращений на чтение, и очень очень редко
От: · Великобритания  
Дата: 09.08.18 21:10
Оценка:
Здравствуйте, VVV, Вы писали:

VVV>·>... но всё равно без разницы. Чтение переменной из одного потока, когда она меняется из другого просто так, без всяких многопоточных штуковин — вещь как повезёт. Например, значение 0 может закешироваться в регистре одного потока и из памяти никогда не читаться и изменения в памяти другим потоком не будут видны. Как мининмум нужен membar.

VVV>·>Приведённый ранее код — ошибочен. Если ты так делаешь в реальном коде — обязательно исправь, это бага.
VVV>Есть волшебное слово volatile — оно спасёт. И не будь столь категоричным. Приведи пример, КАК такой код может сработать неправильно?
Да, если сделать отдельную volatile переменную рядышком с контейнером, то всё в порядке. Просто со стандартными контейнерами не прокатит, там m_size не volatile.

VVV>>>Ещё алгоритм придумался: использовать кольцевой буфер новых/удаляемых объектов. insert/erase двигают tail, doSome двигает head.

VVV>·>Да, такое вроде сработает, но только если doSome выполняется из максимум одного потока.
VVV>ну это ТС пусть смотрит, подходит ему или нет. Судя по этому куску кода — это похоже на код игры, где doSome — в UI потоке, а insert/erase в потоке gameEngine.
Каждый своё увидел. Я увидел что insert/erase это подключение/оключение клиентов, а doSome используется из критических потоков для быстрой раздачи большого количества данных клиентам.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[6]: у него много обращений на чтение, и очень очень редко на запись
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.08.18 02:16
Оценка:
Здравствуйте, ksd, Вы писали:
ksd>и с очень маленькой вероятностью в миллионы раз реже может быть из другого треда subscribers.insert(some) или erase;
ksd>завернуть for в критическую секцию не вариант.
Для такого случая с запасом подойдёт обычный односвязный список с lock-free операциями вставки/удаления через interlocked инструкции.
Единственные сложности — это управление временем жизни элементов списка; т.е. при erase мы рискуем выполнить delete на элементе, который всё ещё кто-то читает.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: нужен контейнер, потокобезопасный, с неинвалидирующимися
От: Кодт Россия  
Дата: 10.08.18 10:48
Оценка:
Здравствуйте, ·, Вы писали:

·>Он написал "завернуть for в критическую секцию не вариант". А обычная база данных как раз это и сделает, правда очень неявно, через 100500 слоёв абстракции. Так что КО немного поспешил.


В критическую секцию, или, всё-таки, в RWLock?
Или RWLock — удел необычных СУБД?
Перекуём баги на фичи!
Re[6]: нужен контейнер, потокобезопасный, с неинвалидирующимися
От: · Великобритания  
Дата: 10.08.18 10:52
Оценка:
Здравствуйте, Кодт, Вы писали:

К>·>Он написал "завернуть for в критическую секцию не вариант". А обычная база данных как раз это и сделает, правда очень неявно, через 100500 слоёв абстракции. Так что КО немного поспешил.

К>В критическую секцию, или, всё-таки, в RWLock?
К>Или RWLock — удел необычных СУБД?
Критическая секция лочит всегда, rwlock — иногда, на этом разница заканчивается. Но lock он и в африке lock... Впрочем, топискстартеру может и не надо избавляться от локов, а достаточно уменьшить их количество.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[7]: у него много обращений на чтение, и очень очень редко на запись
От: Кодт Россия  
Дата: 10.08.18 11:09
Оценка: 37 (1)
Здравствуйте, Sinclair, Вы писали:

S>Для такого случая с запасом подойдёт обычный односвязный список с lock-free операциями вставки/удаления через interlocked инструкции.

S>Единственные сложности — это управление временем жизни элементов списка; т.е. при erase мы рискуем выполнить delete на элементе, который всё ещё кто-то читает.

shared_ptr — lock-free.

Но тут другая история вылезет. Степень дробности того, какие операции мы считаем атомарными. И какие — согласованными по времени (Хоть lock, хоть не lock).
— вызов колбека у одного подписчика
— рассылка на всех подписчиков
— каскад рассылок (т.е. если те начнут сами что-то рассылать или добавлять-удалять...)
— рассылка и что-то плюс-минус вокруг неё на усмотрение рассылающей стороны (например, дождаться квитанции от подписчика — понятно, что на этом отрезке нельзя просто так взять и убить ни подписчика, ни отправителя)

— удаление синхронное (в деструкторе происходит содержательная работа, например, удаляется сеть зависимых объектов)
— сборка мусора асинхронная, "когда получится" (даже с shared_ptr)

— рассылка синхронная (отправитель знает, что когда он вернулся из функции "разослать всё" — всё разослано; возможно, не всем — кого-то убили, кого-то добавили, ну тем не повезло)
— асинхронная (заявку оформил, дальше оно само когда очередь дойдёт)

— каскады событий происходят в одном потоке или в разных (в первом случае синхронность нам навязана, во втором — мы ещё должны за неё побороться)

Это уже архитектурные вопросы.

Я видел некое дерьмо, когда невинная рассылка приводила к длительной вибрации всей сети объектов, причём даже в одном потоке. И вот там вышибание табуретки из-под цикла рассылки — как нефиг делать.
И естественно, никакое лок-фри тут не поможет, равно как рекурсивные мьютексы (а вот нерекурсивные помогут: предсказуемо повесят программу или кинут исключение).
Перекуём баги на фичи!
Re[8]: у него много обращений на чтение, и очень очень редко на запись
От: uzhas Ниоткуда  
Дата: 10.08.18 12:43
Оценка:
Здравствуйте, Кодт, Вы писали:

К>shared_ptr — lock-free.


ну вот не надо вводить читателей в заблуждение
чтение еще можно назвать lock-free. запись в shared_ptr не делают lock-free

К>Но тут другая история вылезет.


тут вылезет как минимум ABA-problem
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.