Здравствуйте, ioni, Вы писали:
I>В качестве тестовых экспериментов попробовал набросать реализацию I>получилось вот что, но вот беда, при прогоне падает
I>в связи с этим вопрос это всегда так для lock free или я чего то упустил
Мне кажется, так и должно было быть. Тут банальный конкурентный доступ к ресурсу — никакой синхронизации, ничего нету . Конечно он будет падать.
Здравствуйте, Glоbus, Вы писали:
G>Здравствуйте, ioni, Вы писали:
I>>В качестве тестовых экспериментов попробовал набросать реализацию I>>получилось вот что, но вот беда, при прогоне падает
I>>в связи с этим вопрос это всегда так для lock free или я чего то упустил
G>Мне кажется, так и должно было быть. Тут банальный конкурентный доступ к ресурсу — никакой синхронизации, ничего нету . Конечно он будет падать.
вообще то так и задумывалось, так работают lock free алгоритмы
а синхронизация есть
Re: про lock free stack
От:
Аноним
Дата:
22.01.08 10:32
Оценка:
Здравствуйте, ioni, Вы писали:
I>В качестве тестовых экспериментов попробовал набросать реализацию I>получилось вот что, но вот беда, при прогоне падает
I>в связи с этим вопрос это всегда так для lock free или я чего то упустил
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, ioni, Вы писали:
I>>В качестве тестовых экспериментов попробовал набросать реализацию I>>получилось вот что, но вот беда, при прогоне падает
I>>в связи с этим вопрос это всегда так для lock free или я чего то упустил
А>может нужна синхронизация в destroy_node?
если ее добавить то тогда весь смысл теряется
меня интересует именно как народ, который пишет и пользуется
такими алгоритмами, справляется с этой проблемой
Здравствуйте, ioni, Вы писали:
I>В качестве тестовых экспериментов попробовал набросать реализацию I>получилось вот что, но вот беда, при прогоне падает
I>в связи с этим вопрос это всегда так для lock free или я чего то упустил
bool pop(T& val)
{
volatile node* next = 0;
do
{
next = head_->next_;
if(next == NULL) { return false; }
}
while( !cas(((long*)(&(head_->next_))), ((long)next), ((long)(next->next_))) ); //здесь может разыменовываться указатель next (next->next_),
// на уже удаленный в другом потоке объект
val = next->value_;
destroy_node(next);
return true;
}
Это не единственная проблема в этом коде, здесь присутствует также проблема "ABA", заменяемый c помощью CAS, указатель может претерпеть ряд изменений (например, со значения A на B и затем снова на A), но при этом CAS не "провалиться". Для решения подобного рода проблем, существует ряд технологий: Safe Memory Reclamation (SMR), пару ссылок по RCU и vZOOM здесь
Здравствуйте, ioni, Вы писали:
I>В качестве тестовых экспериментов попробовал набросать реализацию I>получилось вот что, но вот беда, при прогоне падает
Полностью согласен с Quasi.
По мелочам ещё.
Фейковый нод, который ты выделяешь в конструкторе не нужен. Если ты посмотришь на код внимательно, то заметишь, что ты всегда используешь только head_->next_, но не head_. Это, конечно, не принципиально, но затрудняет восприятие — если ты 100 раз видел алгоритм в одной записи, а потом немного в другой, то это сбивает с толку
volatile'ов лучше переставить, чем недоставить. В pop() ты объявляешь volatile node* next, хотя обращаешься и к next, а не только к next->next_. Я бы объявил как volatile node* volatile next. Сложно сказать, может ли это повлять на корректность в данном случае. Но поверь, это не те проблемы, с которыми ты хочешь иметь дело
Здравствуйте, remark, Вы писали:
R>Фейковый нод, который ты выделяешь в конструкторе не нужен. Если ты посмотришь на код внимательно, то заметишь, что ты всегда используешь только head_->next_, но не head_. Это, конечно, не принципиально, но затрудняет восприятие — если ты 100 раз видел алгоритм в одной записи, а потом немного в другой, то это сбивает с толку
С этом сложно надеяться на работоспособность кода. Ты же в один и тот же hp пишешь из всех потоков!
Плюс get_hazard() должна возвращать node<T>**, а не node<T>*. Ты же так ничего вообще не запишешь в hp! Ты пишешь всегда во временный объект.
#StoreLoad барьер памяти! Если ты делаешь критическую последовательность из load следующего за store, то скорее всего всегда нужен #StoreLoad барьер памяти. В противном случае тебе просто на разрешить конкуренцию между двумя конкурирующими потоками.
Здравствуйте, ioni, Вы писали:
I>В качестве тестовых экспериментов попробовал набросать реализацию I>получилось вот что, но вот беда, при прогоне падает I>в связи с этим вопрос это всегда так для lock free или я чего то упустил
Рекомендую посмотреть на реализацию стека от M$, которая есть в WinAPI. Она полностью рабочая.
ABA проблему они решают с помощью т.н. ABA counter. Т.е. якорь вершины стека выглядит как:
struct anchor
{
node* head;
unsigned counter;
};
counter инкрементируется при каждом добавлении (или удалении) элемента. Это защается от того, что ты увидишь тот же head, но при этом head->next уже будет другой. И соотв. для этого они применяют уже InterlockedCompareExchange64().
Возможность обращения к удалённой памяти они решают гораздо более интересно.
pop() выглядит примерно так:
bool pop(T& val)
{
volatile node volatile* next;
for (;;)
{
bool fail = false;
__try
{
do
{
next = head_->next_;
if(next == 0)
return false;
}
while ( !cas(((long*)(&(head_->next_))), ((long)next), ((long)(next->next_))) );
}
__catch (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
fail = true;
}
if (!fail)
break;
}
val = next->value_;
destroy_node(next);
return true;
}
Очень занятно. Ещё более занятно, как они на 64-битной платформе запихивают в 64 бита: 1. указатель на следующий нод, 2. 16-ти битное кол-во элементов в стеке, 3. ABA счётчик (на 9 бит).
Спасибо за конструктивную критику
сейчас перепишу посмотрю что выйдет
как же это не просто написать простейший контейнер с lock free
R>add_id() надо защитить критической секцией! Блин, ты же её из разных потоков вызываешь!
ну тут я решил что раз lock free то везде
R>Тут, видимо, должен быть assert. Иначе будет утечка памяти, если тут произойдёт return.
согласен но это пока все таки тестовый пример и на такие детали не обращаешь внимания
R>С этом сложно надеяться на работоспособность кода. Ты же в один и тот же hp пишешь из всех потоков!
да тут промашка вышла исправлю
R>Плюс get_hazard() должна возвращать node<T>**, а не node<T>*. Ты же так ничего вообще не запишешь в hp! R>Ты пишешь всегда во временный объект.
поправлю
R>volatile!!!
добавлю везде
R>#StoreLoad барьер памяти! Если ты делаешь критическую последовательность из load следующего за store, то скорее всего всегда нужен R>#StoreLoad барьер памяти. В противном случае тебе просто на разрешить конкуренцию между двумя конкурирующими потоками.
#StoreLoad барьер памяти — это видмо что специфичное для windows искать или уже есть какой нибудь готовый объект ?
R>Как минимум барьер для компилятора. Если hazard сбросится до считывания value, то всё накроется.
а это каким образом обеспечить?
Здравствуйте, remark, Вы писали:
R>Здравствуйте, ioni, Вы писали:
I>>В качестве тестовых экспериментов попробовал набросать реализацию I>>получилось вот что, но вот беда, при прогоне падает I>>в связи с этим вопрос это всегда так для lock free или я чего то упустил
R>Рекомендую посмотреть на реализацию стека от M$, которая есть в WinAPI. Она полностью рабочая.
Здравствуйте, ioni, Вы писали:
I>Здравствуйте, remark, Вы писали:
I>Спасибо за конструктивную критику I>сейчас перепишу посмотрю что выйдет I>как же это не просто написать простейший контейнер с lock free
R>>add_id() надо защитить критической секцией! Блин, ты же её из разных потоков вызываешь! I>ну тут я решил что раз lock free то везде
Ну тогда и делай аккуратно через атомарные изменения. lock free — это не просто убирание критических секций
Вообще, если ты делаешь lock free, то какие-либо стандартные готовые контейнеры — это скорее всего не то, что ты хочешь использовать. Все контейнеры скорее всего в конечном итоге тебе придётся переписать самому (если ты не согласен просто защащать их критическими секциями).
R>>#StoreLoad барьер памяти! Если ты делаешь критическую последовательность из load следующего за store, то скорее всего всегда нужен R>#StoreLoad барьер памяти. В противном случае тебе просто на разрешить конкуренцию между двумя конкурирующими потоками.
I>#StoreLoad барьер памяти — это видмо что специфичное для windows искать или уже есть какой нибудь готовый объект ?
_mm_mfence()
R>>Как минимум барьер для компилятора. Если hazard сбросится до считывания value, то всё накроется. I>а это каким образом обеспечить?
Здравствуйте, ioni, Вы писали:
I>вот что получилось после исправлений I>но результат пока по прежнему не удовлетворительный
А что не удовлетворяет? Падает? Просто досконально изучить код достаточно сложно и нужно много времени, я пока просто написал то, что лежало на поверхности.
Здравствуйте, nen777w, Вы писали:
N>Сорьки а что есть сабж? N>Для чего и в каких ситуациях надо, если можно в двух словах. N>Приведенный код изучать не охота пока.
неееее все контейнеры, я не собираюсь переписывать
меня сейчас интересует подход и во что в принципе
выливается реализация lock freeи с точки зрения трудоемкости
и с точки зрения стабильности работы, поэтому был выбран самый простой контейнер
и пока результат меня не удовлетворяет (в теории выходит все гладко)