Модераторы: Daevaorn

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> C++0X atomic, асинхронное удаление из lock_free stack 
:(
    Опции темы
Леопольд
Дата 28.11.2010, 00:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



http://cppjournal.blogspot.com/
Здесь есть статья про lock_free программирование. Для асинхронного удаления элемента используются весьма сложные конструкции. Я вижу решение таким. Довольно просто, и эффективно. Но, может bыть я что-то недопонимаю, и на самом деле здесь bаги?
неbольшой тест LWS
Код
#include <atomic>
#include <memory>

template <typename T>
class stack
{
private:
    struct node
    {
        node * next;
        std::unique_ptr<T> data;

        node(const T& d, node* n = 0)
            :next(n), data(new T(d)) {}

        node() : next(&stack<T>::end), data(std::unique_ptr<T>()) {}

        node(node const&) = delete;
        node & operator=(node const&) = delete;
    };

    std::atomic<node *> m_head;
    std::atomic_uint m_size;
    static node end;

public:
    void push(const T& data)
    {
        node* new_node = new node(data, m_head.load());
        while (!m_head.compare_exchange_weak(
            new_node->next,
            new_node));

        ++m_size;
    }

    std::unique_ptr<T> pop()
    {
        node* old_head=m_head.load();
        while (!m_head.compare_exchange_weak(
            old_head,
            old_head->next));

        if(old_head != &end )
        {
            --m_size;
            auto ret = move(old_head->data);
            delete old_head;
            return move(ret);
        }
        return std::unique_ptr<T>();
    }

    std::size_t size() {return m_size;}

    stack() : m_head(&end), m_size(0) {}
};

template <typename T>
typename stack<T>::node stack<T>::end;


Это сообщение отредактировал(а) Леопольд - 28.11.2010, 01:22


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 28.11.2010, 01:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Вроде bы раbотает, даже если сперва запустить потоки, которые выкидывают элементы из стека...
Код
#include <iostream>
#include <future>

stack<char> s;
enum { elemQuantity = 1000000 };
void push()
{
    for(std::size_t i = 0; i < elemQuantity; ++i)
        s.push(0);
}
void pop()
{
    auto tr = std::async(std::launch::async, push);

    for(std::size_t i = 0; i < elemQuantity; ++i)
        s.pop();

    tr.wait();

}


int main()
{
    auto tr1 = std::async(std::launch::async, pop);
    auto tr2 = std::async(std::launch::async, pop);

    tr1.wait();
    tr2.wait();

    std::cout << "s.size() = " << s.size() << std::endl;

    return 0;
}
http://liveworkspace.org/code/394a8b9144ffe5cb97781cc55598cdb9

Это сообщение отредактировал(а) Леопольд - 28.11.2010, 01:56


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
boostcoder
Дата 28.11.2010, 04:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


pattern`щик
****


Профиль
Группа: Завсегдатай
Сообщений: 5458
Регистрация: 1.4.2010

Репутация: 49
Всего: 110



Цитата(Леопольд @  28.11.2010,  00:47 Найти цитируемый пост)
http://cppjournal.blogspot.com/
Здесь есть статья про lock_free программирование

там их несколько. о какой именно идет речь?
второе - есть либа relacy. о ней тоже говорится в третьей статье.
PM WWW   Вверх
azesmcar
Дата 28.11.2010, 07:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Ну для начала скажу, что тут есть проблемы не только с очисткой памяти, здесь не предусмотрен pop() на пустом стеке.
Что касается проблемы очистки памяти, опишу ситуацию.
В стеке 10 элементов, 2 потока одновременно выполняют pop().
Код

std::unique_ptr<T> pop()
{
    node* old_head=m_head.load();
    while (!m_head.compare_exchange_weak(old_head, old_head->next));
    if(old_head != &end )
    {
        --m_size;
        auto ret = move(old_head->data);
        delete old_head;
        return move(ret);
    }
    return std::unique_ptr<T>();
}

1 поток выполняет все строчки до 4-ой. Выполняет конструкцию
while (!m_head.compare_exchange_weak(old_head, old_head->next));
compare_exchange_weak возвращает false, т.е. ожидается еще одна итерация и поток передает управеление другому потоку
2 поток входит в pop(), выполняет все действия, удаляет head (так-как old_head != &end), возвращает значение и передает управление 1-ому потоку.
1 поток пытается выполнить еще одну итерацию в цикле while (!m_head.compare_exchange_weak(old_head, old_head->next));
но old_head указывает на удаленную область памяти и old_head->next - обращение к ней, что ведет за собой неопределенное поведение.

И еще .. пользя от функции size() здесь довольно сомнительная. По сути она возвращает программисту размер, который когда-то точно был размером этого стека, не более. smile 

Это сообщение отредактировал(а) azesmcar - 28.11.2010, 07:04
PM   Вверх
boostcoder
Дата 28.11.2010, 07:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


pattern`щик
****


Профиль
Группа: Завсегдатай
Сообщений: 5458
Регистрация: 1.4.2010

Репутация: 49
Всего: 110



Цитата(azesmcar @  28.11.2010,  07:00 Найти цитируемый пост)
она возвращает программисту размер, который когда-то точно был размером этого стека, не более.

забавно-то как smile 
PM WWW   Вверх
azesmcar
Дата 28.11.2010, 07:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Отвлекся..вернемся к делу
Цитата(Леопольд @  28.11.2010,  00:47 Найти цитируемый пост)
неbольшой тест LWS

http://liveworkspace.org/code/b6e8617b670b...85227a2cb07ea49
всего лишь несколько последовательных запусков и...
вообще непонятен смысл этой статической переменной, в ее роли мог бы выступать 0, и не надо было бы проверять при удалении.

boostcoder

Ну разве что для логирования может сгодиться, другого применения я не вижу smile 

Цитата(Леопольд @  28.11.2010,  01:53 Найти цитируемый пост)
Вроде bы раbотает, даже если сперва запустить потоки, которые выкидывают элементы из стека...

Такой тест придется запускать ни один раз, чтобы проверить работоспособность. Лучше использовать 
Цитата(boostcoder @  28.11.2010,  04:01 Найти цитируемый пост)
relacy

правда придется немного модифицировать код, но эта жертва, на которую стоит пойти smile 

Это сообщение отредактировал(а) azesmcar - 28.11.2010, 07:56
PM   Вверх
Леопольд
Дата 28.11.2010, 09:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  28.11.2010,  07:00 Найти цитируемый пост)
И еще .. пользя от функции size() здесь довольно сомнительная.
Это "для сеbя", надо разоbраться с relacy, а это время.

Цитата(azesmcar @  28.11.2010,  07:00 Найти цитируемый пост)
на удаленную область памяти и old_head->next - обращение к ней, что ведет за собой неопределенное поведение.
Так и знал, что что-то упустил. Спасиbо!

Цитата(azesmcar @  28.11.2010,  07:00 Найти цитируемый пост)
Ну для начала скажу, что тут есть проблемы не только с очисткой памяти, здесь не предусмотрен pop() на пустом стеке. ... вообще непонятен смысл этой статической переменной, в ее роли мог бы выступать 0, и не надо было бы проверять при удалении.
Для этого end и нужна. Она зацикливает конец списка на сеbя.


Это сообщение отредактировал(а) Леопольд - 28.11.2010, 11:50


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 28.11.2010, 09:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(boostcoder @  28.11.2010,  07:03 Найти цитируемый пост)
забавно-то как  smile 
 smile 
Полегчало?

Лучший спосоb разоbраться - сделать свой, возможно уbогий, вариант.


Это сообщение отредактировал(а) Леопольд - 28.11.2010, 10:23


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 28.11.2010, 10:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  28.11.2010,  09:37 Найти цитируемый пост)
Для этого end и нужна. Она зацикливает конец списка на сеbя.

Да, не заметил
Цитата(Леопольд @  28.11.2010,  00:47 Найти цитируемый пост)
next(&stack<T>::end)


Цитата(Леопольд @  28.11.2010,  09:37 Найти цитируемый пост)
Так и знал, что что-то упустил. Спасиbо!

Пожалуйста.

PM   Вверх
Леопольд
Дата 28.11.2010, 14:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



поbорол при помощи статического указателя (только это уже не lock_free, а wait_free):
Код
#include <cassert>
#include <atomic>
#include <memory>

template <typename T>
class stack
{
private:
    struct node
    {
        node * next;
        std::unique_ptr<T> data;

        node(const T& d, node* n = 0)
            :next(n), data(new T(d)) {}

        node() : next(0), data(std::unique_ptr<T>()) {}

        node(node const&) = delete;
        node & operator=(node const&) = delete;
    };

    std::atomic<node *> m_head;
    static node pend;

public:
    void push(const T& data)
    {
        node* new_node = new node(data, 0);

        node * head = m_head.exchange(&pend);
        while(head == &pend)
        {
            head = m_head.exchange(&pend);
        }
        new_node->next = head;

        m_head.store(new_node);
    }

    std::unique_ptr<T> pop()
    {
        node * head = m_head.exchange(&pend);
        while(head == &pend)
        {
            head = m_head.exchange(&pend);
        }
        if(head)
        {
            m_head.store(head->next);
            auto ret = move(head->data);
            delete head;
            return move(ret);
        }
        m_head.store(0);
        return std::unique_ptr<T>();
    }
    stack() : m_head(0) {}
};
template <typename T>
typename stack<T>::node stack<T>::pend;
Недостаток, много раbоты в холостую (spin lock). Достоинство, простота реализации. Теперь пора вторую статью читать. smile

Рельна ли ситуация, когда поток подменил указатель и внезапно умер, не завершив раbоту? Если да, то bудет deadlock.

Это сообщение отредактировал(а) Леопольд - 28.11.2010, 23:00


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 28.11.2010, 16:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



А в чем замысел? Почему compare_exchange заменен на if и exchange?
Вообще комментарии бы не помешали, не знаю будет работать или нет, детально не смотрел, но насколько я понял
Цитата(Леопольд @  28.11.2010,  14:55 Найти цитируемый пост)
много раbоты в холостую.

 smile 
и эта работа в холостую по сути лишает преимущества перед блокировками.

Цитата(Леопольд @  28.11.2010,  14:55 Найти цитируемый пост)
Если да, то bудет deadlock.

дедлока тут по определению быть не может. Дедлок может возникнуть только при блокировании, а тут нет блокирования.

Это сообщение отредактировал(а) azesmcar - 30.11.2010, 10:49
PM   Вверх
Леопольд
Дата 28.11.2010, 17:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  28.11.2010,  16:31 Найти цитируемый пост)
А в чем замысел? Почему compare_exchange заменен на if и exchange?
Когда смотришь с уровня новичка, всё выглядит весьма запутанно. Пытаюсь распутать. Здесь получился spin lock. 
compare_exchange сложнее читать, он неинтуитивно записывает новое значение атомарной переменной по ссылке в первый аргумент. 

Это сообщение отредактировал(а) Леопольд - 28.11.2010, 17:37


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 28.11.2010, 17:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  28.11.2010,  17:14 Найти цитируемый пост)
Здесь получился spin lock.

spin lock - тоже lock, но в user-mode и никак не может быть wait-free.
wait-free функция означает, что любой поток, начавший выполнять эту функцию закончит выполнение за N-ое количество шагов, и N никак не зависит от других потоков.
Цитата(Леопольд @  28.11.2010,  17:14 Найти цитируемый пост)
Когда смотришь с уровня новичка, всё выглядит весьма запутанно. Пытаюсь распутать.

Там слишком мало информации, это всего лишь базовые сведения, если интересует тема могу подкинуть литературу, но только на английском.

Это сообщение отредактировал(а) azesmcar - 28.11.2010, 17:22
PM   Вверх
Леопольд
Дата 28.11.2010, 17:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  28.11.2010,  16:31 Найти цитируемый пост)
и эта работа в холостую по сути лишает преимущества перед блокировками.
Вроде как не совсем. Нет переключения контекста, но тоже есть куча минусов.

Цитата(azesmcar @  28.11.2010,  17:20 Найти цитируемый пост)
могу подкинуть литературу, но только на английском.
Не откажусь от ссылок на признанных авторов в данной теме. Спасиbо! Английский даже лучше, иногда перевод тяжелее понять.

Добавлено @ 17:35
Цитата(azesmcar @  28.11.2010,  17:20 Найти цитируемый пост)
и никак не может быть wait-free.
Цитата
Терминология
Lock-free – lock-free считается та процедура, для которой гарантируется прогресс как минимум одного потока, выполняющего эту процедуру. Другие потоки могут ждать, но один поток минимум должен прогрессировать.

Wait-free – операция называется wait-free в том случае, если она завершается за определенное количество шагов, не зависящих от состояние и действий других потоков.
 Действительно, показалось что по смыслу подходит, только перепутал одно с другим...


Это сообщение отредактировал(а) Леопольд - 28.11.2010, 18:07


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 28.11.2010, 18:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  28.11.2010,  17:20 Найти цитируемый пост)
spin lock - тоже lock
Получается, что compare_exchange тот же spin lock.



--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 28.11.2010, 21:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  28.11.2010,  17:29 Найти цитируемый пост)
Не откажусь от ссылок на признанных авторов в данной теме. Спасиbо! Английский даже лучше, иногда перевод тяжелее понять.

Завтра пришлю.

Цитата(Леопольд @  28.11.2010,  18:24 Найти цитируемый пост)
Получается, что compare_exchange тот же spin lock.

Нет, spin-lock - пустая итерация в ожидании, пока другой поток не изменит состояния некой переменной, т.е. это блокировка, mutex работает точно также, просто ожидание не тратит процессорных ресурсов, а compare_exchange ничего не блокирует, он атомарно производит некое действие, никакой другой поток не дожидается его завершения, чтобы продолжить свою работу.

Это сообщение отредактировал(а) azesmcar - 28.11.2010, 21:03
PM   Вверх
mes
Дата 28.11.2010, 21:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


любитель
****


Профиль
Группа: Участник Клуба
Сообщений: 7954
Регистрация: 14.1.2006

Репутация: 144
Всего: 250



Цитата(azesmcar @  28.11.2010,  20:00 Найти цитируемый пост)
Завтра пришлю.

если в личку, просьба и мне выслать smile


--------------------
PM MAIL WWW   Вверх
azesmcar
Дата 28.11.2010, 21:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(mes @  28.11.2010,  21:04 Найти цитируемый пост)
если в личку, просьба и мне выслать  

выложу сюда smile

Добавлено через 40 секунд
просто много ссылок в закладках на работе, лень снова искать.
PM   Вверх
Леопольд
Дата 28.11.2010, 22:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  28.11.2010,  21:00 Найти цитируемый пост)
compare_exchange ничего не блокирует
Я, конечно, имел ввиду в цикле. Если, груbо говоря, 300 процессов bудут постоянно менять переменную, кто-то из них может начать голодать. 
Здесь за флажок принимается не какое-то, заранее определённое значение, а смена значения. Если не изменилось, то поменять, но только один процесс сможет это сделать. Остальные, перейдут на следующую итерацию. 

Цитата(azesmcar @  28.11.2010,  21:00 Найти цитируемый пост)
spin-lock - пустая итерация в ожидании, пока другой поток не изменит состояния некой переменной
Т.е., наверное, можно сказать, что это spin lock наоbорот - пустая итерация (а может и не пустая) в ожидании, пока другие, bолее удачливые потоки не перестанут изменять состояние переменной.

Получается, что если spin lock затрагивает такое же количество тактов, то производительность не упадёт, и шансы на голодание останутся на том же уровне.

Это сообщение отредактировал(а) Леопольд - 28.11.2010, 22:44


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 29.11.2010, 08:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  28.11.2010,  22:17 Найти цитируемый пост)
Я, конечно, имел ввиду в цикле. Если, груbо говоря, 300 процессов bудут постоянно менять переменную, кто-то из них может начать голодать. 

Это не делает из любой итерации блокировку. Эта итерация не блокирует другие потоки, как это делает spin lock, но количество итераций все таки зависит от того, что делают другие потоки, потому эта функция не wait-free.

Статьи
эта статья на русском, не помешает ознакомиться
Что такое модель памяти?
немного википедии
http://en.wikipedia.org/wiki/Non-blocking_algorithm
и далее все на языке Шекспира
Herb Sutter - Lock-Free Code: A False Sense of Security
Andrei Alexandrescu - Lock-Free Data Structures
Andrei Alexandrescu, Maged Michael - Lock-Free Data Structures with Hazard Pointers
Thomas Edward Hart Thesis
Maged Michael - Hazard Pointers
Herb Sutter - A Principle-Based Sequential Memory Model for Microsoft Native Code Platforms
Geoff Langdale - Lock-Free programming
ABA problem


Это сообщение отредактировал(а) azesmcar - 29.11.2010, 08:43
PM   Вверх
Леопольд
Дата 29.11.2010, 09:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



azesmcar, премного благодарен!


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 30.11.2010, 09:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(Леопольд @  28.11.2010,  18:24 Найти цитируемый пост)
Получается, что compare_exchange тот же spin lock.
Цитата(Леопольд @  28.11.2010,  22:17 Найти цитируемый пост)
Получается, что если spin lock затрагивает такое же количество тактов, то производительность не упадёт, и шансы на голодание останутся на том же уровне.
Бывает такой бред пишу... smile
На spin lock'е при вытесняющей (preemptive) многозадачности, очень легко dead lock получить. Когда процесс с более низким приоритетом был прерван сразу после того, как он сделал spin lock. В lock free это невозможная ситуация.

Это сообщение отредактировал(а) Леопольд - 30.11.2010, 09:59


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 30.11.2010, 09:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  30.11.2010,  09:57 Найти цитируемый пост)
На spin lock'е при вытесняющей (preemptive) многозадачности, очень легко dead lock получить

это называется live-lock.

Добавлено через 34 секунды
deadlock не тратит процессорные ресурсы.
PM   Вверх
Леопольд
Дата 30.11.2010, 10:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



azesmcar, ясно, спасибо.


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 30.11.2010, 22:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



deleted

Это сообщение отредактировал(а) Леопольд - 30.11.2010, 23:09


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 1.12.2010, 10:48 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Не могу скачать relacy... 
Цитата
Страницы, на которую Вы переходите, не существует. 


Надеялся она мне поможет понять почему это не работает. Пытаюсь написать вариант с очередью на удаление (end зациклен на себя):
Код
#include <atomic>
#include <memory>

template <typename T>
class stack
{
private:
    struct node
    {
        node * next;
        std::shared_ptr<T> data;

        node(const T& d, node * n) :next(n), data(new T(d)) {}

        node(node * n) :next(n), data(std::shared_ptr<T>()) {}

        node(node const&) = delete;
        node & operator=(node const&) = delete;
    };

    std::atomic<node *> m_head;
    std::atomic<node *> m_deleted_head;
    std::atomic<std::size_t> m_active_threads;

    static node end;

public:
    stack() : m_head(&end), m_deleted_head(&end), m_active_threads(0) {}

    void push(const T& data)
    {
        node * new_node = new node(data, m_head.load());

        while(!m_head.compare_exchange_weak(new_node->next, new_node));
    }


    std::shared_ptr<T> pop()
    {
        ++m_active_threads;

        node * head = m_head.load();
        while(!m_head.compare_exchange_weak(head, head->next));

        std::shared_ptr<T> ret(head->data);

        if(head == &end)
        {
            --m_active_threads;
            return ret;
        }

        node * deleted_head = m_deleted_head.load();
        //check for solo thread
        if(!--m_active_threads)
        {
            //if m_deleted_head has not been changed then no threads use nodes from the queue
            if(m_deleted_head.compare_exchange_strong(deleted_head, &end))
            {
                head->next = deleted_head;
                while(head != &end)
                {
                    auto next = head->next;
                    delete head;
                    head = next;
                }
            }
        }
        //in other case just push the node to the deleted list
        else
        {
            head->next = m_deleted_head.load();
            while(!m_deleted_head.compare_exchange_weak(head->next, head));
        }

        return ret;
    }
};

template <typename T>
typename stack<T>::node stack<T>::end(&stack<T>::end);


Это сообщение отредактировал(а) Леопольд - 1.12.2010, 11:12


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 1.12.2010, 11:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  1.12.2010,  10:48 Найти цитируемый пост)
Не могу скачать relacy... 

На гугле запретили выкладывать zip файлы.
Качать надо отсюда, добавлю в статью.
http://sites.google.com/site/1024cores/downloads
PM   Вверх
Леопольд
Дата 1.12.2010, 11:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



azesmcar, спасибо!

Так вроде работает, теперь надо с relacy попробовать протестить.
Код

        //check for solo thread
        if(m_active_threads.load() == 1)
        {
            //if m_deleted_head has not been changed theт no threads use nodes from the queue
            if(!--m_active_threads && m_deleted_head.compare_exchange_strong(deleted_head, &end))
            {
                head->next = deleted_head;
                while(head != &end)
                {
                    auto next = head->next;
                    delete head;
                    head = next;
                }
            }
        }


Не, нифига, здесь утечка памяти...

Это сообщение отредактировал(а) Леопольд - 1.12.2010, 11:32


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 1.12.2010, 22:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



relacy не помог...  smile 
http://liveworkspace.org/code/8ed457a04766...847bee388deb79b
Код
#include <memory>

#include <cstdio>
#include <relacy/relacy_std.hpp>

template <typename T>
class concurent_stack
{
private:
    struct node
    {
        node * next;
        std::shared_ptr<T> data;

        node(const T& d, node* n) :next(n), data(std::make_shared<T>(d)) {}
        node(node* n) :next(n), data(std::shared_ptr<T>()) {}

        node(node const&);
        node & operator=(node const&);
    };

    std::atomic<node *> m_head;
    std::atomic<node *> m_deleted_head;
    std::atomic<std::size_t> m_active_threads;

    static node end;

public:
    concurent_stack() : m_head(&end), m_deleted_head(&end), m_active_threads(0) {}

    void push(const T& data)
    {
        node * new_node = new node(data, m_head($).load());

        while(!m_head($).compare_exchange_weak(new_node->next, new_node));
    }


    std::shared_ptr<T> pop()
    {
        m_active_threads($).fetch_add(1);

        node * head = m_head($).load();
        while(!m_head($).compare_exchange_strong(head, head->next));

        node * deleted_head = m_deleted_head($).load();
        if(m_active_threads($).fetch_sub(1) - 1 == 0 && m_deleted_head($).compare_exchange_strong(deleted_head, &end))
        {
            std::shared_ptr<T> ret;

            if(head == &end)
            {
                head = deleted_head;
            }
            else
            {
                head->data.swap(ret);
                head->next = deleted_head;
            }

            while(head != &end)
            {
                node * next = head->next;
                delete head;
                head = next;
            }

            return ret;
        }

        if(head != &end)
        {
            std::shared_ptr<T> ret;
            head->data.swap(ret);

            head->next = m_deleted_head($).load();
            while(!m_deleted_head($).compare_exchange_weak(head->next, head));

            return ret;
        }

        return head->data;
    }

    ~concurent_stack()
    {
        node * head = m_head($).load();
        while(head != &end)
        {
            node * next = head->next;
            delete head;
            head = next;
        }

        head = m_deleted_head($).load();
        while(head != &end)
        {
            node * next = head->next;
            delete head;
            head = next;
        }
    }
};
template <typename T>
typename concurent_stack<T>::node concurent_stack<T>::end(&concurent_stack<T>::end);



unsigned const thread_count = 4;
unsigned nodes_count = 100000;

struct stack_test : rl::test_suite<stack_test, thread_count>
{
    stack_test() {}

    concurent_stack<char> * stack;

    // executed in single thread before main thread function
    void before()
    {
        stack = new concurent_stack<char>();
    }

    // main thread function
    void thread(unsigned index)
    {
        if(index % 2)
        {
            for(std::size_t i = 0; i < nodes_count; ++i)
                stack->push(i);
        }
        else
        {
            for(std::size_t i = 0; i < nodes_count; ++i)
                stack->pop();
        }
    }

    // executed in single thread after main thread function
    void after()
    {
        delete stack;
    }

    // executed in single thread after every 'visible' action in main threads
    // disallowed to modify any state
    void invariant()
    {
    }
};


#include <boost/lexical_cast.hpp>

int main(int argc, char * argv[])
{
    if(argc > 1)
    {
        nodes_count = boost::lexical_cast<std::size_t>(argv[1]);
    }

    rl::test_params p;
    p.execution_depth_limit = 1000000000;

    rl::simulate<stack_test>(p);

    return 0;
}


запустил на ночь, для 200000 операций push/pop параллельно.

Это сообщение отредактировал(а) Леопольд - 1.12.2010, 23:07


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
boostcoder
Дата 1.12.2010, 23:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


pattern`щик
****


Профиль
Группа: Завсегдатай
Сообщений: 5458
Регистрация: 1.4.2010

Репутация: 49
Всего: 110



Цитата(Леопольд @  1.12.2010,  22:57 Найти цитируемый пост)
запустил на ночь, для 2000000 операций push/pop параллельно

где? на LWS ?! smile

Добавлено через 1 минуту и 24 секунды
и relacy на LWS нет. хотя подумываю установить. карман ведь не тянет smile

Добавлено через 2 минуты и 48 секунд
Леопольд, скажи, у тебя есть где реально применить сие?
PM WWW   Вверх
Леопольд
Дата 1.12.2010, 23:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(boostcoder @  1.12.2010,  23:03 Найти цитируемый пост)
где? на LWS ?!
Нет конечно, локально.
Уменьшил до 200000, bоюсь к утру LIVELOCK напишет иначе...

Добавлено @ 23:11
Цитата(boostcoder @  1.12.2010,  23:03 Найти цитируемый пост)
Леопольд, скажи, у тебя есть где реально применить сие? 
Пока нет. Просто проbую свои силы. Потом хеш-таbлицу хочу написать и распараллелить A* smile
Статью Тиграна прочёл и, неожиданно увлёкся. Кажется мне что в ИИ, bудущее за lock free алгоритмами.


Это сообщение отредактировал(а) Леопольд - 1.12.2010, 23:14


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
boostcoder
Дата 1.12.2010, 23:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


pattern`щик
****


Профиль
Группа: Завсегдатай
Сообщений: 5458
Регистрация: 1.4.2010

Репутация: 49
Всего: 110



установил relacy.
вот тест: http://liveworkspace.org/code/08782ad50b2d...3c7ef1f00810096

Добавлено @ 23:24
только не понимаю что там выводится, и что должно выводится smile 

Это сообщение отредактировал(а) boostcoder - 1.12.2010, 23:25
PM WWW   Вверх
azesmcar
Дата 1.12.2010, 23:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  1.12.2010,  22:57 Найти цитируемый пост)
relacy не помог...   

чем именно он должен был помочь?

Цитата(boostcoder @  1.12.2010,  23:03 Найти цитируемый пост)
Леопольд, скажи, у тебя есть где реально применить сие? 

если нужно куда-то применить, советую взглянуть на libcds

Цитата(boostcoder @  1.12.2010,  23:23 Найти цитируемый пост)
установил relacy.

О! Отлично.

Леопольд

Александреску в одной из статей использует такой трюк.
Инкапсулируется некий тип (например map)
Чтение - wait-free безо всяких итераций, просто возвращение объекта.
Запись - создание копии, добавление новой записи и замена внутреннего объекта.
Ну и конечно же опять встает вопрос удаления старой копии.
Это можно построить на шаблоне и применять эту технику для любого типа, но естественно, это эффективно только тогда, когда запись является редким явлением. Упор делается на  скорость чтения высокая.

Это сообщение отредактировал(а) azesmcar - 1.12.2010, 23:43
PM   Вверх
Леопольд
Дата 2.12.2010, 06:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Код
10stack_test
iterations: 1000
total time: 1423390
throughput: 0
Что значить throughput в relacy? Почему-то только в ночных тестах равен нулю.

Добавлено @ 06:46
Цитата(azesmcar @  1.12.2010,  23:32 Найти цитируемый пост)
ем именно он должен был помочь?
Где-то, видимо двойной delete. Не могу понять где... 
Мне, вроде бы, удалось обойти добавление эелементов обратно в очередь на удаление. Это может сильно поднять производительность.
http://liveworkspace.org/code/8ed457a04766...847bee388deb79b
Цитата
*** glibc detected *** source.cpp.bin: free(): invalid pointer: 0xb3762e38 ***


Добавлено @ 06:49
Цитата(boostcoder @  1.12.2010,  23:23 Найти цитируемый пост)
только не понимаю что там выводится
Поток не успел завершить раbоту до достижения 
Код
p.execution_depth_limit = 100000;
 Это, как я понял, количество ($) через которые он прошёл.


Это сообщение отредактировал(а) Леопольд - 2.12.2010, 09:11


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 2.12.2010, 07:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  2.12.2010,  06:44 Найти цитируемый пост)
 Это, как я понял, количество ($) через которые он прошёл.

Цитата

Also you can specify 'execution_depth_limit' parameter - used for livelock detection. All executions with trace longer than execution_depth_limit will be treated as livelocked (or non-terminating).


Цитата(Леопольд @  2.12.2010,  06:44 Найти цитируемый пост)
Что значить throughput в relacy? Почему-то в только в ночных тестах равен нулю.

Этого не знаю... smile 

PM   Вверх
Леопольд
Дата 2.12.2010, 09:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Оно, кстати, иногда работает, хотя нагрузка серьёзная. Два потока "выкидывают" элементы другие два "вставляют", в сумме 2000000 элементов.
http://liveworkspace.org/code/705bf2998c43...9a3bced3e591522
Сперва пытался сделать вариант с возвратом элементов обратно. Но он просто "вешался" под такой нагрузкой.

Может я как-то неправильно тестирую? Не получается воспроизвести...
Код
#include <cstdio>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include "relacy/relacy_std.hpp"

template <typename T>
class concurent_stack
{
private:
    struct node
    {
        node * next;
        boost::shared_ptr<T> data;
        node(const T& d, node* n) :next(n), data(boost::make_shared<T>(d)) {}
        node(node* n) :next(n), data(boost::shared_ptr<T>()) {}
        node(node const&);
        node & operator=(node const&);
    };
    std::atomic<node *> m_head;
    std::atomic<node *> m_deleted_head;
    std::atomic<std::size_t> m_active_threads;

    std::atomic<std::size_t> m_size;
    std::atomic<std::size_t> m_deleted_queue_size;

    static node end;

public:
    concurent_stack() : m_head(&end), m_deleted_head(&end), m_active_threads(0), m_size(0), m_deleted_queue_size(0) {}
    void push(const T& data)
    {
        node * new_node = new node(data, m_head($).load());
        while(!m_head($).compare_exchange_weak(new_node->next, new_node));
        m_size($).fetch_add(1);
    }
    boost::shared_ptr<T> pop()
    {
        m_active_threads($).fetch_add(1);
        node * head = m_head($).load();
        while(!m_head($).compare_exchange_weak(head, head->next));

        //1.Загружаю указатель на начало очереди на удаление
        node * deleted_head = m_deleted_head($).load();
        //2. умешьшаю счётчик потоков и если он стал равен нулю значит нет потоков, которые работают с тем же m_head
        //3. пытаюсь сделать один strong CAS указателя m_deleted_head и зацикленного на себя указателя &end (признак конца очереди).
        if(m_active_threads($).fetch_sub(1) - 1 == 0 && m_deleted_head($).compare_exchange_strong(deleted_head, &end))
        {
            //Если удалось подменить m_deleted_head с первого раза, значит ни один поток не успел его поменять и список можно спокойно удалять.
            boost::shared_ptr<T> ret;
            if(head == &end)
            {
                head = deleted_head;
            }
            else
            {
                head->data.swap(ret);
                m_size($).fetch_sub(1);

                head->next = deleted_head;
            }
            while(head != &end)
            {
                node * next = head->next;
                if(next != &end) m_deleted_queue_size($).fetch_sub(1);
                delete head;
                head = next;
            }
            return ret;
        }

        if(head != &end)
        {
            //Если не удалось подменить m_deleted_head, то запихнуть удаляемый элемент в очередь на удаление.
            boost::shared_ptr<T> ret;
            head->data.swap(ret);
            m_size($).fetch_sub(1);

            head->next = m_deleted_head($).load();
            while(!m_deleted_head($).compare_exchange_weak(head->next, head));

            m_deleted_queue_size($).fetch_add(1);
            return ret;
        }

        //если стек был пуст
        return boost::shared_ptr<T>();
    }
    ~concurent_stack()
    {
        node * head = m_head($).load();
        while(head != &end)
        {
            node * next = head->next;
            delete head;
            head = next;
        }
        head = m_deleted_head($).load();
        while(head != &end)
        {
            node * next = head->next;
            delete head;
            head = next;
        }
    }

    std::size_t size()
    {
        return m_size($).load();
    }

    std::size_t del_queue_size()
    {
        return m_deleted_queue_size($).load();
    }
};
template <typename T>
typename concurent_stack<T>::node concurent_stack<T>::end(&concurent_stack<T>::end);




unsigned const thread_count = 32;
unsigned nodes_count = 8;
unsigned non_deleted_queue_total_length = 0;
unsigned non_deleted_queues_quantity = 0;

struct stack_test : rl::test_suite<stack_test, thread_count>
{
    stack_test() {}
    concurent_stack<char> * stack;
    std::size_t non_deleted_nodes;
    // executed in single thread before main thread function
    void before()
    {
        stack = new concurent_stack<char>();
        non_deleted_nodes = 0;
    }
    // main thread function
    void thread(unsigned index)
    {
        if(index % 2)
        {
            for(std::size_t i = 0; i < nodes_count; ++i)
                stack->push(i);
        }
        else
        {
            for(std::size_t i = 0; i < nodes_count; ++i)
                if(!stack->pop())
                    ++non_deleted_nodes;
        }
    }
    // executed in single thread after main thread function
    void after()
    {
        if(stack->size())
        {
            RL_ASSERT(stack->size() == non_deleted_nodes);
        }
        non_deleted_queue_total_length += stack->del_queue_size();
        ++non_deleted_queues_quantity;
        delete stack;
    }
    // executed in single thread after every 'visible' action in main threads
    // disallowed to modify any state
    void invariant()
    {
    }
};
#include <boost/lexical_cast.hpp>
int main(int argc, char * argv[])
{
    if(argc > 1)
    {
        nodes_count = boost::lexical_cast<std::size_t>(argv[1]);
    }
    rl::test_params p;
    p.execution_depth_limit = 1000000000;
    rl::simulate<stack_test>(p);
    std::cout << "average length of non deleted queues = " << (non_deleted_queue_total_length / (double)non_deleted_queues_quantity) << std::endl;
    return 0;
}
Код
10stack_test
iterations: 1000
total time: 5860
throughput: 170

average length of non deleted queues = 26.922

Process returned 0 (0x0)   execution time : 5.877 s


Это сообщение отредактировал(а) Леопольд - 2.12.2010, 11:45


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 2.12.2010, 09:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Добавь хоть комментарии и опиши алгоритм.
PM   Вверх
Леопольд
Дата 2.12.2010, 11:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  2.12.2010,  09:34 Найти цитируемый пост)
Добавь хоть комментарии и опиши алгоритм. 
Он похож на тот, который в статье. Основное отличие, работа с очередью удалённых - m_deleted_head:

1. Загружаю m_deleted_head (указатель на начало очереди на удаление)
2. умешьшаю счётчик потоков и если он стал равен нулю (значит нет потоков, которые работают с тем же m_head - указатель на "выкидываемый" элемент).
3. пытаюсь сделать один strong CAS указателя m_deleted_head и зацикленного на себя указателя &end (признак конца очереди).
Рассчёт на то, что если удалось подменить m_deleted_head с первого раза, значит ни один поток не успел его поменять и список можно спокойно удалять.

Если не удалось подменить, то пихаю удаляемый элемент в очередь на удаление.

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

накидал это в виде комментариев в предыдущий пост
http://forum.vingrad.ru/act-ST/f-92/t-3162.../p-2258093.html



Это сообщение отредактировал(а) Леопольд - 2.12.2010, 11:37


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 2.12.2010, 12:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Заработало!  smile (поменял 1. и 2. местами  и бага "прибил")
8000000 элементов, 4 потока: 2 удаляют, 2 добавляют.
http://liveworkspace.org/code/a1e0bde56b2e...720510a50a6379c

relacy тоже удовлетворён...
Код
#include <cstdio>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include "relacy/relacy_std.hpp"

template <typename T>
class concurent_stack
{
private:
    struct node
    {
        node * next;
        boost::shared_ptr<T> data;
        node(const T& d, node* n) :next(n), data(boost::make_shared<T>(d)) {}
        node(node* n) :next(n), data(boost::shared_ptr<T>()) {}
        node(node const&);
        node & operator=(node const&);
    };
    std::atomic<node *> m_head;
    std::atomic<node *> m_deleted_head;
    std::atomic<std::size_t> m_active_threads;

    //these two are used for testing and quality metrics
    std::atomic<std::size_t> m_size;
    std::atomic<std::size_t> m_deleted_queue_size;

    static node end;

public:
    concurent_stack() : m_head(&end), m_deleted_head(&end), m_active_threads(0), m_size(0), m_deleted_queue_size(0) {}
    void push(const T& data)
    {
        node * new_node = new node(data, m_head($).load());
        while(!m_head($).compare_exchange_weak(new_node->next, new_node));
        m_size($).fetch_add(1);
    }
    boost::shared_ptr<T> pop()
    {
        m_active_threads($).fetch_add(1);
        node * head = m_head($).load();
        while(!m_head($).compare_exchange_weak(head, head->next));

        //check what no one use the same m_head
        if(m_active_threads($).fetch_sub(1) - 1 == 0)
        {
            node * deleted_head = m_deleted_head($).load();
            //check what no one use any node from the m_deleted_head
            if(m_deleted_head($).compare_exchange_strong(deleted_head, &end) && deleted_head != &end)
            {
                boost::shared_ptr<T> ret;
                if(head == &end)
                {
                    //always try to release the queue of deleted elements
                    //return invalid pointer for the empty stack
                    head = deleted_head;
                }
                else
                {
                    head->data.swap(ret);
                    m_size($).fetch_sub(1);

                    head->next = deleted_head;
                }
                while(head != &end)
                {
                    node * next = head->next;
                    if(next != &end) m_deleted_queue_size($).fetch_sub(1);
                    delete head;
                    head = next;
                }
                return ret;
            }
        }

        if(head != &end)
        {
            //push the deleted element to the queue - m_deleted_head
            boost::shared_ptr<T> ret;
            head->data.swap(ret);
            m_size($).fetch_sub(1);

            head->next = m_deleted_head($).load();
            while(!m_deleted_head($).compare_exchange_weak(head->next, head));

            m_deleted_queue_size($).fetch_add(1);
            return ret;
        }

        //return invalid pointer for the empty stack
        return boost::shared_ptr<T>();
    }

    //this should be called by a sole thread, then no more threads  work with the instance
    ~concurent_stack()
    {
        node * head = m_head($).load();
        while(head != &end)
        {
            node * next = head->next;
            delete head;
            head = next;
        }
        head = m_deleted_head($).load();
        while(head != &end)
        {
            node * next = head->next;
            delete head;
            head = next;
        }
    }

    //used for testing
    std::size_t size()
    {
        return m_size($).load();
    }
    //used for quality metrics
    std::size_t del_queue_size()
    {
        return m_deleted_queue_size($).load();
    }
};
template <typename T>
typename concurent_stack<T>::node concurent_stack<T>::end(&concurent_stack<T>::end);



//relacy test suite
unsigned const thread_count = 32;
unsigned nodes_count = 8;
unsigned non_deleted_queue_total_length = 0;
unsigned non_deleted_queues_quantity = 0;

struct stack_test : rl::test_suite<stack_test, thread_count>
{
    stack_test() {}
    concurent_stack<char> * stack;
    std::size_t non_deleted_nodes;
    // executed in single thread before main thread function
    void before()
    {
        stack = new concurent_stack<char>();
        non_deleted_nodes = 0;
    }
    // main thread function
    void thread(unsigned index)
    {
        if(index % 2)
        {
            for(std::size_t i = 0; i < nodes_count; ++i)
                stack->push(i);
        }
        else
        {
            for(std::size_t i = 0; i < nodes_count; ++i)
                if(!stack->pop())
                    ++non_deleted_nodes;
        }
    }
    // executed in single thread after main thread function
    void after()
    {
        if(stack->size())
        {
            RL_ASSERT(stack->size() == non_deleted_nodes);
        }
        non_deleted_queue_total_length += stack->del_queue_size();
        ++non_deleted_queues_quantity;
        delete stack;
    }
    // executed in single thread after every 'visible' action in main threads
    // disallowed to modify any state
    void invariant()
    {
    }
};
#include <boost/lexical_cast.hpp>
int main(int argc, char * argv[])
{
    if(argc > 1)
    {
        nodes_count = boost::lexical_cast<std::size_t>(argv[1]);
    }
    rl::test_params p;
    p.execution_depth_limit = 1000000000;
    rl::simulate<stack_test>(p);
    std::cout << "average length of non deleted queues = " << (non_deleted_queue_total_length / (double)non_deleted_queues_quantity) << std::endl;
    return 0;
}
Код
10stack_test
iterations: 1000
total time: 8700
throughput: 114

average length of non deleted queues = 5.4

Process returned 0 (0x0)   execution time : 8.717 s
Да и показатели улучшились.

Если всего 2 потока удаляют то, average length of non deleted queues = 0.09.

P.S. Пожалуй этот вариант уже не так "убог"...
P.S.S А вообще, очень даже ничего! smile

Это сообщение отредактировал(а) Леопольд - 2.12.2010, 15:06


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 2.12.2010, 12:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



блин smile убогая фигня, опять падает... smile
на одноядерном процессоре, почти сразу.


бага "прибил". Всё чудесно!  smile 


Если кто-то сможет его "уронить", буду весьма признателен. 

Это сообщение отредактировал(а) Леопольд - 2.12.2010, 15:09


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 2.12.2010, 16:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Сколько всего изменилось smile я не успеваю.

Цитата(Леопольд @  2.12.2010,  12:52 Найти цитируемый пост)
бага "прибил"

Мало того, что нашел..так еще и прибил smile 

Цитата(Леопольд @  2.12.2010,  11:03 Найти цитируемый пост)
http://forum.vingrad.ru/act-ST/f-92/t-3162.../p-2258093.html

ага, посмотрю.

Цитата(Леопольд @  2.12.2010,  12:52 Найти цитируемый пост)
Если кто-то сможет его "уронить", буду весьма признателен. 

Добавь в relacy количество потоков и итераций и оставь на ночь.
PM   Вверх
Леопольд
Дата 2.12.2010, 19:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  2.12.2010,  16:26 Найти цитируемый пост)
Мало того, что нашел..так еще и прибил
Плохо приbил...  smile Надо передохнуть, уже не сооbражаю ничего...
http://liveworkspace.org/code/1d1704acbdea...fc11a835f900d4e



--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 2.12.2010, 20:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Я бы хорошенько пересмотрел этот код. Это и так сложно, а у тебя усложнено еще больше. Комментарии нужны в первую очередь для себя, раздели все на мелкие функции, это заметно облегчит и чтение и понимание того, что происходит. Представить в уме возможные варианты выполнения для нескольких потоков, которые в любой момент могут делать все, что угодно и так сложно, а это еще усложняется кодом. Для начала напиши список, который работает, но с утечками, протестируй, а потом добавляй очистку памяти отдельными функциями. Отдели как нибудь ту часть, которая потенциально может содержать ошибку (т.е. часть очистки памяти) от той, которая протестирована и работает. На данный момент код функции pop слишком большой, чтобы можно было найти в нем ошибку.
PM   Вверх
Леопольд
Дата 3.12.2010, 08:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



azesmcar, пытаюсь рафинировать, потихоньку...

И прихожу к вывод что нельзя использовать обычную связку malloc/free
Запускаю два потока, один добавляет, другой удаляет. Между собой у них только одна атомарная переменная (указатель на голову стека), relacy тесты проходят с опцией (p.search_type = rl::fair_full_search_scheduler_type;) которая всё пространство состояний тестирует. Всё ок.
Но, как только пытаюсь запустить на одноядерной машине (Ubuntu 10.04, g++ 4.5.1): 
Цитата
*** glibc detected *** /home/andrey/proj/try_c++0x/bin/Debug/try_c++0x: malloc(): memory corruption (fast): 0x09a0b9a0 ***
Это, видимо, если вызвать malloc до того как free закончит работу.

Сейчас соображу спин лок на выделение памяти и проверю. Google говорит что есть такая штука как lock free malloc



Это сообщение отредактировал(а) Леопольд - 3.12.2010, 11:36


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 3.12.2010, 09:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Вот рафинированный код.
LWS relacy
Код
#include <cstdio>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include "relacy/relacy_std.hpp"

template <typename T>
class concurent_stack
{
private:
    struct node
    {
        node * next;
        boost::shared_ptr<T> data;

        node(const T& d, node* n) :next(n), data(boost::make_shared<T>(d)) {}

        node(node const&);
        node & operator=(node const&);
    };

    std::atomic<node *> m_head;
    std::atomic<node *> m_deleted_head;
    std::atomic<std::size_t> m_threads_on_lap;

public:
    concurent_stack() : m_head(0), m_deleted_head(0), m_threads_on_lap(0) {}

    void push(const T& data)
    {
        node * new_node = new node(data, m_head($).load());

        while(!m_head($).compare_exchange_weak(new_node->next, new_node));
    }


    boost::shared_ptr<T> pop()
    {
        boost::shared_ptr<T> ret;

        //begining of the lap (начало "заезда")
        m_threads_on_lap($).fetch_add(1);

        node * head = m_head($).load();
        while(head && !m_head($).compare_exchange_weak(head, head->next));

        if(head)
        {
            ret.swap(head->data);
            head->next = m_deleted_head($).load();
            while(!m_deleted_head($).compare_exchange_weak(head->next, head));
        }
        else
        {
            head = m_deleted_head($).load();
        }

        //1. check this is the last in the lap (кто приехал последний, тот делает "грязную" работу)
        //2. check that no one has changed m_deleted_head since the previous condition (но только если трек сободен)
        //   that means that everithin in the deleted queue can be safely released
        if(m_threads_on_lap($).fetch_sub(1) == 1 && m_deleted_head($).compare_exchange_strong(head, 0))
        {
            while(head)
            {
                node * next = head->next;
                delete head;
                head = next;
            }
        }

        return ret;
    }

    //this method must be called if there is no any thread with reference to the stack instance left
    bool compact()
    {
        node * head = m_deleted_head($).exchange(0);
        bool ret = head;
        while(head)
        {
            node * next = head->next;
            delete head;
            head = next;
        }
        return ret;
    }

    //this method must be called if there is no any thread with reference to the stack instance left
    bool clear()
    {
        node * head = m_head($).exchange(0);
        bool ret = head;
        while(head)
        {
            node * next = head->next;
            delete head;
            head = next;
        }
        return ret;
    }

    //this method must be called if there is no any thread with reference to the stack instance left
    ~concurent_stack()
    {
        clear();
        compact();
    }
};


//relacy test suite
unsigned const thread_count = 3;
unsigned nodes_count = 0;
unsigned non_deleted_queue_total_length = 0;
unsigned non_deleted_queues_quantity = 0;
struct stack_test : rl::test_suite<stack_test, thread_count>
{
    stack_test() {}
    concurent_stack<char> stack;
    std::size_t non_deleted_nodes;
    // executed in single thread before main thread function
    void before()
    {
        non_deleted_nodes = 0;
//        for(std::size_t i = 0; i < nodes_count * thread_count; ++i)
//            stack.push(i);

    }
    // main thread function
    void thread(unsigned index)
    {
        if(index % 2)
        {
            for(std::size_t i = 0; i < nodes_count; ++i)
                stack.push(i);
        }
        else
        {
            for(std::size_t i = 0; i < nodes_count; ++i)
                if(!stack.pop())
                    ++non_deleted_nodes;
        }
    }
    // executed in single thread after main thread function
    void after()
    {
//        if(stack.size())
//        {
//            RL_ASSERT(stack.size() == non_deleted_nodes);
//        }
//        non_deleted_queue_total_length += stack.del_queue_size();
//        ++non_deleted_queues_quantity;
        stack.clear();
        stack.compact();
    }
    // executed in single thread after every 'visible' action in main threads
    // disallowed to modify any state
    void invariant()
    {
    }
};

void concurent_stack_relacy_test(std::size_t nodes)
{
    nodes_count = nodes;
    rl::test_params p;
    p.execution_depth_limit = 1000000000;
    p.search_type = rl::fair_full_search_scheduler_type;
    rl::simulate<stack_test>(p);
//    std::cout << "average length of non deleted queues = " << (non_deleted_queue_total_length / (double)non_deleted_queues_quantity) << std::endl;
}


Не получается уронить (если не увеличивать количество потоков), видимо на сервере несколько ядер:
http://liveworkspace.org/code/260469bbd4f7...d03e4ad1d4b26e7
на работа одноядерная машина, на ней падает почти сразу.


Это сообщение отредактировал(а) Леопольд - 4.12.2010, 08:24


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 3.12.2010, 10:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(Леопольд @  3.12.2010,  09:46 Найти цитируемый пост)
на работа одноядерная машина, на ней падает почти сразу.

С таким спин локом, тоже падает...
Код
        bool busy = false;
        while(!m_malloc_lock.compare_exchange_weak(busy, true)) busy = false;
            node * new_node = static_cast<node *>(::malloc(sizeof(node)));
        m_malloc_lock.exchange(false);
//...
        bool busy = false;
        while(!m_malloc_lock.compare_exchange_weak(busy, true)) busy = false;
            ::free(head);
        m_malloc_lock.exchange(false);

Может я зря грешу на malloc/free?

P.S.
Временами, relacy на спин локе зависает наглухо...

P.P.S Пока malloc не отработает, память в список не записывается. Но он может быть вызван до того, как free закончит работать (а может ещё операционка что-то делает с ОЗУ?).
Что ж, это за зверь такой: "lock free malloc"?

Это сообщение отредактировал(а) Леопольд - 3.12.2010, 11:39


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 3.12.2010, 13:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Очевидно что отдельно от push, pop отрабатывает нормально:
сперва забиваю стек (10 миллионов элементов), потом 100 потоков начинают дёргать pop
что радует, когда последний поток закончил работать, вся память полностью освободилась (в отличие от предыдущих "кривоногих" версий).
Шанс получить "хвост" в конце работы всех потоков пренебрежительно мал. И чем больше потоков, тем меньше шансов. Самый невезучий подчищает за всеми.

http://liveworkspace.org/code/1d76fb72fafa...af5403f5ae0b02e

На одноядерной (Ubuntu 10.04 g++4.5.1) тоже работает без сбоев.



Тут вывод поинтереснее: http://liveworkspace.org/code/81347b6f4ba2...09c75a0fbcc31a5
Код
stack initial size = 10000001

thread 44 has finished
stack size = 2642345
unreleased queue size = 3660181

thread 48 has finished
stack size = 2589150
unreleased queue size = 3707905

thread 18 has finished
stack size = 2537135
unreleased queue size = 3759920

thread 4 has finished
stack size = 2414236
unreleased queue size = 3559001

thread 7 has finished
stack size = 2314566
unreleased queue size = 3529441

thread 59 has finished
stack size = 2230629
unreleased queue size = 3521894

thread 10 has finished
stack size = 2162419
unreleased queue size = 3484866

thread 49 has finished
stack size = 2128617
unreleased queue size = 3518668

thread 9 has finished
stack size = 2117141
unreleased queue size = 3486890

thread 29 has finished
stack size = 2007886
unreleased queue size = 3290803

thread 41 has finished
stack size = 1999288
unreleased queue size = 3277614

thread 0 has finished
stack size = 1864715
unreleased queue size = 3174104

thread 12 has finished
stack size = 1839678
unreleased queue size = 3199141

thread 14 has finished
stack size = 1825162
unreleased queue size = 3213657

thread 23 has finished
stack size = 1812291
unreleased queue size = 3226528

thread 65 has finished
stack size = 1736287
unreleased queue size = 3216530

thread 55 has finished
stack size = 1726989
unreleased queue size = 3225828

thread 6 has finished
stack size = 1725092
unreleased queue size = 3227725

thread 28 has finished
stack size = 1673922
unreleased queue size = 3195378

thread 13 has finished
stack size = 1650342
unreleased queue size = 3218958

thread 66 has finished
stack size = 1611956
unreleased queue size = 3110658

thread 30 has finished
stack size = 1594565
unreleased queue size = 3128049

thread 77 has finished
stack size = 1573693
unreleased queue size = 2946389

thread 3 has finished
stack size = 1563860
unreleased queue size = 2912076

thread 40 has finished
stack size = 1459541
unreleased queue size = 2855257

thread 21 has finished
stack size = 1429850
unreleased queue size = 2841847

thread 46 has finished
stack size = 1407019
unreleased queue size = 2778359

thread 31 has finished
stack size = 1406712
unreleased queue size = 2778666

thread 2 has finished
stack size = 1366253
unreleased queue size = 2764690

thread 62 has finished
stack size = 1334392
unreleased queue size = 2796551

thread 17 has finished
stack size = 1306581
unreleased queue size = 2738526

thread 37 has finished
stack size = 1306031
unreleased queue size = 2739076

thread 39 has finished
stack size = 1305696
unreleased queue size = 2739411

thread 81 has finished
stack size = 1272058
unreleased queue size = 2639992

thread 36 has finished
stack size = 1269776
unreleased queue size = 2642274

thread 63 has finished
stack size = 1229666
unreleased queue size = 2514145

thread 43 has finished
stack size = 1201958
unreleased queue size = 2518313

thread 50 has finished
stack size = 1143983
unreleased queue size = 2489735

thread 94 has finished
stack size = 1138499
unreleased queue size = 2495219

thread 8 has finished
stack size = 1131589
unreleased queue size = 2396755

thread 56 has finished
stack size = 1126783
unreleased queue size = 2401561

thread 24 has finished
stack size = 1103550
unreleased queue size = 2367027

thread 22 has finished
stack size = 1090631
unreleased queue size = 2375937

thread 53 has finished
stack size = 1087141
unreleased queue size = 2379427

thread 85 has finished
stack size = 1085738
unreleased queue size = 2380830

thread 45 has finished
stack size = 1083072
unreleased queue size = 2383496

thread 83 has finished
stack size = 1075907
unreleased queue size = 2390661

thread 97 has finished
stack size = 1067448
unreleased queue size = 2277675

thread 5 has finished
stack size = 1058138
unreleased queue size = 2189389

thread 38 has finished
stack size = 1053359
unreleased queue size = 2194168

thread 72 has finished
stack size = 1041007
unreleased queue size = 2206520

thread 87 has finished
stack size = 1038184
unreleased queue size = 2148721

thread 96 has finished
stack size = 1033579
unreleased queue size = 2127381thread thread 67 has finished
stack size = 982341
unreleased queue size = 2168983

thread 26 has finished
stack size = 979851
unreleased queue size = 2141491

thread 25 has finished
stack size = 980879
unreleased queue size = 2170445

84 has finished
stack size = 1008982
unreleased queue size = 2142342



thread 57 has finished
stack size = 967586
unreleased queue size = 2110870

thread 19 has finished
stack size = 961933
unreleased queue size = 2116523

thread 33 has finished
stack size = 951173
unreleased queue size = 2081633

thread 68 has finished
stack size = 949458
unreleased queue size = 2083348

thread 71 has finished
stack size = 936008
unreleased queue size = 1866626

thread 51 has finished
stack size = 919471
unreleased queue size = 1675180

thread 20 has finished
stack size = 919292
unreleased queue size = 1665446

thread 99 has finished
stack size = 898377
unreleased queue size = 1643406

thread 16 has finished
stack size = 847777
unreleased queue size = 1694006

thread 54 has finished
stack size = 842433
unreleased queue size = 1699350

thread 64 has finished
stack size = 833509
unreleased queue size = 1708274

thread 75 has finished
stack size = 815762
unreleased queue size = 1644418

thread 89 has finished
stack size = 747456
unreleased queue size = 1585001

thread 78 has finished
stack size = 734042
unreleased queue size = 1377750

thread 93 has finished
stack size = 703387
unreleased queue size = 1408405

thread 88 has finished
stack size = 696701
unreleased queue size = 1415091

thread 34 has finished
stack size = 633226
unreleased queue size = 1154936

thread 32 has finished
stack size = 616764
unreleased queue size = 1078504

thread 91 has finished
stack size = 602533
unreleased queue size = 1092735

thread 82 has finished
stack size = 571859
unreleased queue size = 1038972

thread 1 has finished
stack size = 560558
unreleased queue size = 1005796

thread 73 has finished
stack size = 553461
unreleased queue size = 1012893

thread 42 has finished
stack size = 543609
unreleased queue size = 1022743

thread 15 has finished
stack size = 532778
unreleased queue size = 987826

thread 60 has finished
stack size = 504121
unreleased queue size = 1016483

thread 70 has finished
stack size = 499674
unreleased queue size = 955828

thread 95 has finished
stack size = 437003
unreleased queue size = 909881

thread 58 has finished
stack size = 433946
unreleased queue size = 912938

thread 80 has finished
stack size = 411691
unreleased queue size = 935194

thread 69 has finished
stack size = 374313
unreleased queue size = 950588

thread 92 has finished
stack size = 360115
unreleased queue size = 921313

thread 98 has finished
stack size = 318550
unreleased queue size = 933257

thread 76 has finished
stack size = 314318
unreleased queue size = 937489

thread 74 has finished
stack size = 309366
unreleased queue size = 942441

thread 35 has finished
stack size = 306487
unreleased queue size = 945320

thread 47 has finished
stack size = 238476
unreleased queue size = 748038

thread 86 has finished
stack size = 200546
unreleased queue size = 699032

thread 52 has finished
stack size = 130401
unreleased queue size = 468879

thread 79 has finished
stack size = 92753
unreleased queue size = 332552

thread 61 has finished
stack size = 83495
unreleased queue size = 209966

thread 90 has finished
stack size = 54268
unreleased queue size = 47116

thread 27 has finished
stack size = 35949
unreleased queue size = 12567

thread 11 has finished
stack size = 1
unreleased queue size = 0


exit code: 0, execution time: 9.5688


Это сообщение отредактировал(а) Леопольд - 3.12.2010, 14:19


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 3.12.2010, 14:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Добавил немного комментариев, теперь давай прочитаем код
Код

boost::shared_ptr<T> pop()
{
    boost::shared_ptr<T> ret;

    // увеличиваем количество потоков в функции pop
    m_threads_on_lap($).fetch_add(1);
    
    // убираем элемент из списка
    node * head = m_head($).load();
    while(head && !m_head($).compare_exchange_weak(head, head->next));
    
    // если элемент получен, т.е. список был не пуст
    if(head)
    {
        // устанавливаем возвращаемое значение
        ret.swap(head->data);

        // добавляем элемент в начало списка элементов на удаление
        head->next = m_deleted_head($).load();
        while(!m_deleted_head($).compare_exchange_weak(head->next, head));
    }

    // если это последний поток, т.е. больше в функции pop потоков на данный момент нет
    if(m_threads_on_lap($).fetch_sub(1) - 1 == 0)
    {
        // проверяем, что никто не менял m_deleted_head с того момента, как мы добавили туда элемент
        if (m_deleted_head($).compare_exchange_strong(head, 0))
        {
            // удаляем все элементы, подлежащие удалению
            while(head)
            {
                node * next = head->next;
                delete head;
                head = next;
            }
        }
    }
    return ret;
}

начнем со строки 24.
тут ты сперва проверяешь, что это единственный поток в функции pop (кстати все время хочу спросить почему value - 1 == 0 а не value == 1?).
потом проверяешь, что никто не менял m_deleted_head и он все еще равен head-у, т.е. поменять может в том случае, если в этом промежутке создался другой поток.
дальше, если ничего менялось ты удаляешь все элементы.
Вот тут то и проблема. Представь, что 1 твой поток дошел до строки 33, удали элемент, на который на данный момент указывает m_deleted_head (так как m_deleted_head и head указывают на ту же область памяти) и передал управление второму, который пытается добавить следующий элемент в список на удаление, но натыкается на UB на строке 20.
В примере я не зря очищал m_deleted_head и отделял от него весь список в локальный указатель, это делалось для того, чтобы другой поток в это время не смог с ним работать.
PM   Вверх
Леопольд
Дата 3.12.2010, 14:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  3.12.2010,  14:19 Найти цитируемый пост)
который пытается добавить следующий элемент в список на удаление, но натыкается на UB на строке 20.
Если поток1 находится на двадцатой строке, то m_threads_on_lap($).fetch_sub(1) - 1 == 0 не выполнится. Если потокN дошёл до 9-й строки, то он никак не может получить тот же указатель "head" из-за 10 строки.

Это сообщение отредактировал(а) Леопольд - 3.12.2010, 14:25


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 3.12.2010, 14:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  3.12.2010,  14:23 Найти цитируемый пост)
Если кто-то находится на двадцатой строке, то m_threads_on_lap($).fetch_sub(1) - 1 == 0 не выполнится. А если выполнится, значит ни один поток не владеет тем же указателем.

Ты забываешь, что это две разные проверки и они НЕ атомарны. Потому я и отделил их в два отдельных if-а, чтобы было нагляднее.
Возможна такая ситуация: первая проверка выполняется, перед началом выполнения второй, другой поток входит в функцию pop.

Добавлено через 1 минуту и 56 секунд
Код

    // если это последний поток, т.е. больше в функции pop потоков на данный момент нет
    if(m_threads_on_lap($).fetch_sub(1) - 1 == 0)
    {
        // В ЭТОТ МОМЕНТ ДРУГОЙ ПОТОК МОЖЕТ НАЧАТЬ ВЫПОЛНЕНИЕ pop
        if (m_deleted_head($).compare_exchange_strong(head, 0))
        {



Это сообщение отредактировал(а) azesmcar - 3.12.2010, 14:25
PM   Вверх
Леопольд
Дата 3.12.2010, 14:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  3.12.2010,  14:24 Найти цитируемый пост)
Ты забываешь, что это две разные проверки и они НЕ атомарны.
Не, про это я уже не забываю. smile
Здесь проблема появляется когда параллельно начинаешь запихивать элементы в стек.

Можно и сильнее нагрузить, результать будет тот же.
100 потоков удаляют 10000000 элементов из стека размером 100000001 элементов:
http://liveworkspace.org/code/81347b6f4ba2...09c75a0fbcc31a5

Цитата
// В ЭТОТ МОМЕНТ ДРУГОЙ ПОТОК МОЖЕТ НАЧАТЬ ВЫПОЛНЕНИЕ pop
пусть выполняет. Он уже никак не сможет получить тот же указатель из m_head. Если потокY в этот момент заменит m_deleted_head, то у него указатель на начало очереди на удаление, в которой и "наш" сидит уже. Оставляем очищать ему.

Это сообщение отредактировал(а) Леопольд - 3.12.2010, 14:43


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 3.12.2010, 14:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  3.12.2010,  14:19 Найти цитируемый пост)
тут ты сперва проверяешь, что это единственный поток в функции pop (кстати все время хочу спросить почему value - 1 == 0 а не value == 1?).
Это проверка нужна не для того, что-бы гарантировать единственность. Она гарантирует что все потоки, которые зашли в pop прошли эту отметку и закончили работу со своими указателями. Почему не сравниваю с единицей сам не пойму smile

Я пытался найти способ ответвить один поток в своё русло при помощи одного только стчётчика, но так и не смог. Но тут помогла compare_exchange_strong на m_deleted_head. Тот, кто смог её выполинть, гарантированно владеет списком указателей, с которыми больше никто не работает. Поэтому вся работа с указателями происходит до того как уменьшается счётчик. Я и имя ему такое дал, что бы с гонками ассоциировалось. Чистит хвосты самый нерадивый. smile
Это, кстати, наглядно из логов видно (пару постов назад)


Это сообщение отредактировал(а) Леопольд - 3.12.2010, 14:58


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 3.12.2010, 15:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Да, это я напутал..ты тоже тут выделяешь список в отдельную переменную, но проблема все равно та же.
Цитата(azesmcar @  3.12.2010,  14:24 Найти цитируемый пост)
// В ЭТОТ МОМЕНТ ДРУГОЙ ПОТОК МОЖЕТ НАЧАТЬ ВЫПОЛНЕНИЕ pop

процитирую статью
Код

void try_reclaim(node* old_head)
{
    if(threads_in_pop == 1)
    {
        node* nodes_to_delete = to_be_deleted.exchange(NULL);
        if(--threads_in_pop == 0)
        {
            delete_nodes(nodes_to_delete);
        }
        else if(nodes_to_delete)
        {
            chain_node_list(nodes_to_delete);
        }
        delete old_head;
    }
    else
    {
        chain_one_node(old_head);
        --threads_in_pop;
    }
}

Цитата

еще одна проверка значения счетчика нужна для того, чтобы удостовериться, что никакой другой поток не начал выполняться в промежутке между первой проверкой и обменом указателя to_be_deleted на NULL.

описываю проблемную ситуацию, номера строк смотри в моем коде.
1-ый поток входит в pop
2-ой поток входит в pop
3-ий поток входит в pop
1-ый поток продолжает выполнение и доходит до строки 27, так-как остальные два потока пока не инкрементировали значение счетчика
2-ой поток начинает выполнение и доходит до строки 10
3-ий поток начинает выполнение и доходит до строки 10
2-ой поток удаляет элемент из списка и дойдя до строки 20 помещает его в список на удаление
1-ый поток продолжает работу и выделяет список в локальную переменную m_deleted_head
1-ый поток доходит до цикла на строке 30 и начинает удалять элементы, включая тот, который туда поместил 2-ой поток
3-ий поток просыпается и все еще ссылается на тот элемент, который поместил в список на удаление 2-ой поток
3-ий поток при попытке выполнить 10-ую строку вылетит с ошибкой

в двух словах: один поток удаляет объект, помещенный в список другим поток, на который ссылается третий.

Это сообщение отредактировал(а) azesmcar - 3.12.2010, 15:06
PM   Вверх
Леопольд
Дата 3.12.2010, 15:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  3.12.2010,  15:02 Найти цитируемый пост)
1-ый поток продолжает работу и выделяет список в локальную переменную m_deleted_head
Этого не будет. 1-ый поток один раз попытается сделать strong CAS, но 2-ой поток изменил его, пэтому 1-ый поток получит false (не сможет выделить список в локальную переменную).

Цитата(azesmcar @  3.12.2010,  15:02 Найти цитируемый пост)
в двух словах: один поток удаляет объект, помещенный в список другим поток, на который ссылается третий.
Но такая бага действительно имела место быть в предыдущих вариантах (по моему, relacy мне её показал).

Вообще, relacy классная штука. Я когда первый вариант ему подсунул, он тут же показал что память течёт рекой... smile

Добавлено @ 15:31
Цитата(azesmcar @  3.12.2010,  15:02 Найти цитируемый пост)
процитирую статью
Вариант из статьи форсирует алгоритм "гулять" по очереди туда-обратно. К тому же, он вставляет очередь обратно по одномоу элементу, постоянно мешая другим потокам, что очень негативно сказывается на производительности. С 10 миллионами элементов он будет работать очень долго. Сперва я пытался сделать что-бы он вставлял обратно очередь сразу всю целиком, но всё равно приходилось "гулять" из конца в конец. Именно поэтому, я решил попробовать сделать иначе. Обойти этот момент. Похоже удалось.

Но возникает какая-то загадочная проблема с выделением и освобождением памяти параллельно.


Это сообщение отредактировал(а) Леопольд - 3.12.2010, 15:36


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 3.12.2010, 15:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  3.12.2010,  15:19 Найти цитируемый пост)
Этого не будет. 1-ый поток один раз попытается сделать strong CAS, но 2-ой поток изменил его, пэтому 1-ый поток получит false (не сможет выделить список в локальную переменную).

Да, верно..невнимательный я что-то, не обратил внимания на if. А тут точно есть проблема в функции pop? Я ничего другого не вижу, вроде все в порядке.
PM   Вверх
Леопольд
Дата 3.12.2010, 15:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  3.12.2010,  15:35 Найти цитируемый пост)
А тут точно есть проблема в функции pop?
Похоже что нет. Я пришёл к мнению что с pop всё ОК. Пробовал под большой нагрузкой и relacy всё проверил. 
Цитата(Леопольд @  3.12.2010,  15:19 Найти цитируемый пост)
возникает какая-то загадочная проблема с выделением и освобождением памяти параллельно.



Это сообщение отредактировал(а) Леопольд - 3.12.2010, 15:38


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 3.12.2010, 15:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  3.12.2010,  15:19 Найти цитируемый пост)
Но возникает какая-то загадочная проблема с выделением и освобождением памяти параллельно.

где и как это проявляется?
PM   Вверх
Леопольд
Дата 3.12.2010, 15:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  3.12.2010,  15:38 Найти цитируемый пост)
где и как это проявляется? 
Надо несколько потоков запустить. Половина вставляет элементы, другая половина выкидывает. При большом  количестве потоков и элементов.

P.S. пора домой...


Это сообщение отредактировал(а) Леопольд - 3.12.2010, 15:40


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 3.12.2010, 15:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд, 
Цитата(Леопольд @  3.12.2010,  15:40 Найти цитируемый пост)
Надо несколько потоков запустить. Половина вставляет элементы, другая половина выкидывает. При большом  количестве потоков и элементов.

я теряюсь в твоих исходниках, уже не понимаю которая версия правильная, которая нет..покажи пальцем где и когда происходит.
PM   Вверх
azesmcar
Дата 3.12.2010, 17:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

А у тебя там случайно не bad_alloc вылетает?
PM   Вверх
Леопольд
Дата 3.12.2010, 18:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  3.12.2010,  15:44 Найти цитируемый пост)
я теряюсь в твоих исходниках, уже не понимаю которая версия правильная, которая нет.
вот правильная:
http://forum.vingrad.ru/act-ST/f-92/t-3162.../p-2258868.html

здесь она же, но bез relacy модификаций
http://liveworkspace.org/code/81347b6f4ba2...09c75a0fbcc31a5

Добавлено @ 18:54
Цитата(azesmcar @  3.12.2010,  17:35 Найти цитируемый пост)
А у тебя там случайно не bad_alloc вылетает? 
bыло bы неплохо smile Но оно просто падает.
Цитата
*** glibc detected *** /home/andrey/proj/try_c++0x/bin/Debug/try_c++0x: malloc(): memory corruption (fast): 0x09a0b9a0 ***


Вот, уронил (2 потока вставляют, 2 выкидывают): http://liveworkspace.org/code/34a83f493245...44fb349a193814d


Это сообщение отредактировал(а) Леопольд - 4.12.2010, 09:01


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 4.12.2010, 09:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(Леопольд @  3.12.2010,  18:47 Найти цитируемый пост)
Но оно просто падает.
Подумал что можно попроbовать аллокатор написать. В lock free это, наверное, очень сложная штука (а может просто нереальная). Память фрагментируется совершенно непредсказуемо. Может в этом и проbлема...

Решил попроbовать malloc из libatomic-ops-dev
http://manpages.ubuntu.com/manpages/lucid/...c-malloc.3.html
Отпишусь, когда bудут результаты.

судя по описанию, не подходит
Попроbую почитать это когда время bудет (автор Maged M. Michael).

Цитата
Async-signal-safety: Due to the use of locking in cur-
rent implementations of malloc and free, they are not consid-
ered async-signal-safe [9], i.e., signal handlers are prohibited
from using them.



Это сообщение отредактировал(а) Леопольд - 4.12.2010, 10:10


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 8.12.2010, 09:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Был в небольшом отпуске..вернемся к нашим баранам, на свежую голову и думается легче. Проблема то лежит на самом видном месте
Проверка
Код

if (m_threads_on_lap.fetch_sub(1) - 1 == 0)

Эквивалентна проверке
Код

if (m_threads_on_lap.fetch_sub(1) == 1)

Но почему тут проверяется на единицу? При входе в pop значение инкрементируется 1 раз, а тут декрементируется (тоже один раз). Т.е. После операции fetch_sub(1) переменная указывает на количество потоков в функции pop на данный момент (не считая текущего потока). А значит проверка на 1 означает, что там есть еще один поток, который может создать проблемы smile 
Исправлять естественно вот так
Код

if (m_threads_on_lap.fetch_sub(1) == 0)


PM   Вверх
Леопольд
Дата 8.12.2010, 19:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



azesmcar, fetch_sub возвращает предыдущее значение. Т.е. 1 в данном случае. Если только тут где-то ABA спряталась. Но я не вижу.

Скорее всего, проbлема в аллокации памяти. По отдельности push и pop выполняются bез нареканий, но если смешать...

Везде пишут что стандартные malloc и free раbотают на мьютексе.

Это сообщение отредактировал(а) Леопольд - 8.12.2010, 19:39


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 8.12.2010, 19:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  8.12.2010,  19:21 Найти цитируемый пост)
azesmcar, fetch_sub возвращает предыдущее значение. Т.е. 1 в данном случае. 

Да? Не помню..посмотрел в документации, действительно так и есть..

Цитата(Леопольд @  8.12.2010,  19:21 Найти цитируемый пост)
Скорее всего, проbлема в аллокации памяти.

Там не может быть проблем, в мире полно работающих структур с использованием операторов new и delete. Проблема как я уже говорил в обращении к удаленному участку памяти, надо просто ее найти.

Добавлено через 59 секунд
Цитата(Леопольд @  8.12.2010,  19:21 Найти цитируемый пост)
Везде пишут что стандартные malloc и free раbотают на мьютексе.

каким образом это может помешать работать алгоритму? Это может сказаться на производительности, это может лишить структуру статуса lock-free, но никак не должно мешать ей работать правильно.
PM   Вверх
Леопольд
Дата 8.12.2010, 19:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  8.12.2010,  19:30 Найти цитируемый пост)
каким образом это может помешать работать алгоритму? Это может сказаться на производительности, это может лишить структуру статуса lock-free, но никак не должно мешать ей работать правильно. 

Согласен, алгоритм просто перестаёт bыть lock free.

Вероятнее всего, проbлема во фрагментации памяти. Если выделять память bлоками одинакового размера, то перестаёт падать:
http://liveworkspace.org/code/3e95623f8da7...1b146a08dd3a6e7


Это сообщение отредактировал(а) Леопольд - 8.12.2010, 19:43


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 8.12.2010, 19:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  8.12.2010,  19:40 Найти цитируемый пост)
Вероятнее всего, проbлема во фрагментации памяти. Если выделять память bлоками одинакового размера, то перестаёт падать:

меня все таки не покидает чувство, что ищем то, чего на самом деле нет.. smile 
завтра на работе с утра покопаюсь поглубже..
PM   Вверх
azesmcar
  Дата 9.12.2010, 08:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Провел несколько тестов. Что-то здесь не так.. smile пример из книги тоже не работает. Довольно странно, надо поработать над вопросом поглубже.

Добавлено через 1 минуту и 22 секунды
имеется ввиду на gcc 4.5, к сожалению другой библиотеки для тестов у меня нет.
PM   Вверх
azesmcar
Дата 9.12.2010, 09:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Нашел..в общем аллокатор тут не причем, точнее причем, но не он источник проблемы. Это ABA smile 
Поигрался с алокатором, в итоге
Код

int *p = new int(0);
std::cout << p << std::endl;
delete p;
p = new int(0);
std::cout << p << std::endl;

практически всегда выводит одно и тоже, т.е. если запрашивать память после удаления, системный аллокатор скорее всего вернет ту же область памяти, которую недавно освободил. Это прекрасно объясняет почему разделение push и pop избавляют от проблемы а также то, почему без delete -а все работает - аллокатор не может вернуть старый адрес, так-как тот еще не освобожден.
Позже попробую исправить.

Это сообщение отредактировал(а) azesmcar - 9.12.2010, 09:52
PM   Вверх
azesmcar
Дата 9.12.2010, 10:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



К сожалению далеко не все еще реализовано в GCC smile 
пробую исправить добавлением счетчика-а, но std::atomic<used-defined-type> еще не реализован.
Код

struct node;
struct node_ptr
{
    node* ptr;
    unsigned cnt;
};
struct node
{
    node_ptr next;
    std::shared_ptr<T> data;
    node(const T& d, const node_ptr& n): next(n), data(std::make_shared<T>(d)) {}
};
std::atomic<node_ptr> m_head;
std::atomic<node_ptr> m_deleted_head;
...

идея в том, чтобы инкрементировать значение счетчика, тем самым делая указатели на тот же участок памяти разным в разное время. DCAS по идее должен быть Lock-free на всех современных платформах.

Это сообщение отредактировал(а) azesmcar - 9.12.2010, 10:38
PM   Вверх
Леопольд
Дата 9.12.2010, 11:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  9.12.2010,  09:51 Найти цитируемый пост)
аллокатор не может вернуть старый адрес, так-как тот еще не освобожден.
Боюсь не уловил смысл. Вроде бы, если память ещё не освобождена, то аллокатор должен выделить новый кусок, разве нет? 
Если я правильно понял, то мьютекс здесь смог бы помочь.

А вот по поводу ABA, это, наверное оно самое! Надо понять только как smile


Это сообщение отредактировал(а) Леопольд - 9.12.2010, 11:01


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 9.12.2010, 11:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  9.12.2010,  11:01 Найти цитируемый пост)
Боюсь не уловил смысл. Вроде бы, если память ещё не освобождена, то аллокатор должен выделить новый кусок, разве нет? 

Посмотри этот пример
Код

int *p = new int(0);
std::cout << p << std::endl;
delete p;
p = new int(0);
std::cout << p << std::endl;

выделяется память, возвращается адрес
память освобождается и при следующем выделение памяти аллокатор снова возвращает тот же самый указатель, т.е. выделяет память на том же участке. Тут и возникает ABA, CAS думает, что это тот же указатель, а он другой. В качестве решения можно как-то уникально идентифицировать каждую аллокацию используя счетчик, но тут сложность с реализацией (писал выше об этом). Можно сделать через std::atomic<long long> - его размер 8 байтов, туда можно поместить и счетчик и указатель. Не сильно переносимо конечно, но для теста сойдет smile 

Цитата(Леопольд @  9.12.2010,  11:01 Найти цитируемый пост)
Если я правильно понял, то мьютекс здесь смог бы помочь.

Не понял причем тут мутекс? Это уже блокировка, мы про неблокирующие алгоритмы говорим.


Это сообщение отредактировал(а) azesmcar - 9.12.2010, 11:09
PM   Вверх
Леопольд
Дата 9.12.2010, 11:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  9.12.2010,  11:05 Найти цитируемый пост)
Посмотри этот пример
Это я понял. Я про то предложение, которое процитировал.

Цитата(azesmcar @  9.12.2010,  11:05 Найти цитируемый пост)
Тут и возникает ABA, CAS думает, что это тот же указатель, а он другой. 
Может один и тот же адрес дважды попасть в список на удаление? Вроде нет, он ведь висит там до тех пор, пока память не будет освобождена. Т.е. это возможно только если память может быть выделена на тот же адрес до того как будет освобождена, это было бы довольно странное поведение...
Может ли один и тот же адрес попасть в стек дважды? После чего дважды перекочует в очередь на удаление. По моему, тоже нет, по той же причине.

Не могу понять, где здесь ABA. Но оно вероятнее чем баги в gcc. У меня, кстати, тоже только gcc 4.5.1 под рукой, и на работе и дома.

Цитата(azesmcar @  9.12.2010,  11:05 Найти цитируемый пост)
Не понял причем тут мутекс? Это уже блокировка, мы про неблокирующие алгоритмы говорим.
Это я про стандартные malloc и free. Пока гуглил, не раз попадалось что в gcc они не lock free, т.е. работают с мьютексом.


Можно попробовать проверить, надо добавить std::set<void *> и блокировки при работе с ним. Выделил память, запихнул туда адрес, если он уже там, значит ABA имеет место быть. Освободил память, выкинул адрес из сета. Только вот получится ли воспроизвести ошибку? Блокировка может сделать так, что ошибка пропадёт навсегда. smile

P.S. на работе сроки поджимают, буду сюда заглядывать, но гораздо реже чем раньше.

Это сообщение отредактировал(а) Леопольд - 9.12.2010, 12:06


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 9.12.2010, 16:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Леопольд

Проблема та, которую я описывал еще в первом или во втором предположении smile 

Вот код
Код

std::shared_ptr<T> pop()
{
    std::shared_ptr<T> ret;
    // увеличиваем количество потоков в функции pop
    m_threads_in_pop.fetch_add(1);

    // убираем элемент из списка
    node *head = m_head.load();
    while (head && !m_head.compare_exchange_weak(head, head->next));

    // если элемент получен, т.е. список был не пуст
    if(head)
    {
        // устанавливаем возвращаемое значение
        ret.swap(head->data);
        // добавляем элемент в начало списка элементов на удаление
        head->next = m_deleted_head.load();
        while (!m_deleted_head.compare_exchange_weak(head->next, head));
    }
    // если это последний поток, т.е. больше в функции pop потоков на данный момент нет
    if(m_threads_in_pop.fetch_sub(1) == 1)
    {
        // проверяем, что никто не менял m_deleted_head с того момента, как мы добавили туда элемент
        if (m_deleted_head.compare_exchange_strong(head, 0))
        {
            // удаляем все элементы, подлежащие удалению
            while (head)
            {
                node * next = head->next;
                delete head;
                head = next;
            }
        }
    }
    return ret;
}

Представь, что 1 поток останавливается на выполнении строки
Код

if (m_deleted_head.compare_exchange_strong(head, 0))

к этому моменту количество потоков в функции pop уже декрементировано и равно 0.
до того, как произойдет сравнение другой поток может изменить этот список, на что ты вполне логично ответил
Цитата

Тот, кто смог её выполинть, гарантированно владеет списком указателей, с которыми больше никто не работает. Поэтому вся работа с указателями происходит до того как уменьшается счётчик. Я и имя ему такое дал, что бы с гонками ассоциировалось. Чистит хвосты самый нерадивый. 

т.е. если кто-то изменял список, то проверка
Код

if (m_deleted_head.compare_exchange_strong(head, 0))

не пройдет.

это верно, разве что, если другой поток не изменил этот список, в конце добавив туда элемент с тем же адресом smile 
представь, что твой код остановился на выполнении этой проверки, в head загужен узел A, а потом другой поток удалил несколько элементов, очистил за ними память, добавил еще один, удалил, и последний удаленный имеет тот же адрес, который на данный момент загружен в head в твоем первом потоке. Это возможно, так-как когда твой первый поток остановится на выполнении проверки, счетчик равен нулю, твой узел (A) в списке to_be_deleted и другой поток волен удалить этот элемент.

Вроде бы так..надо подумать еще раз попозже, на свежую голову smile 


Это сообщение отредактировал(а) azesmcar - 9.12.2010, 16:25
PM   Вверх
Леопольд
Дата 9.12.2010, 18:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Наверное, это оно. Попроbую оbойти...

Это сообщение отредактировал(а) Леопольд - 9.12.2010, 19:05


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 9.12.2010, 21:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Да уж. При помощи одной только CAS не получается оbойти ABA, только если опять же вставлять очередь оbратно. LL/SC могло bы спасти.


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 9.12.2010, 21:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  9.12.2010,  21:35 Найти цитируемый пост)
. При помощи одной только CAS не получается оbойти ABA

Надо  добавить счетчик, но std::atomic для пользовательских типов не реализован, можно через std::atomic<long long>, в первых 4-х байтах хранить адрес, в остальных четырех счетчик.

Добавлено через 2 минуты и 17 секунд
или надо алгоритм составить так, чтобы проблемы не возникало.
PM   Вверх
Леопольд
Дата 10.12.2010, 11:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  9.12.2010,  21:47 Найти цитируемый пост)
в первых 4-х байтах хранить адрес
Это только для 32 битных платформ сойдёт.

Цитата(azesmcar @  9.12.2010,  21:47 Найти цитируемый пост)
или надо алгоритм составить так, чтобы проблемы не возникало. 
Придётся. В общем, мало одной только CAS. Надо бы ещё и LL/SC на всех архитектурах реализовать. smile
Или нужен сборщик мусора. Вроде как хотят его добавить в С++...


Это сообщение отредактировал(а) Леопольд - 10.12.2010, 11:24


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Леопольд
Дата 10.12.2010, 11:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



deleted

Это сообщение отредактировал(а) Леопольд - 10.12.2010, 11:40


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 10.12.2010, 13:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  10.12.2010,  11:23 Найти цитируемый пост)
Это только для 32 битных платформ сойдёт.

Ну я писал, что это непереносимо, но тебе все равно для тестов, так что сойдет smile 

Цитата(Леопольд @  10.12.2010,  11:23 Найти цитируемый пост)
Придётся. В общем, мало одной только CAS. Надо бы ещё и LL/SC на всех архитектурах реализовать. 

Почему же, в алгоритме, описанном в статье этой проблемы нет.
PM   Вверх
azesmcar
Дата 10.12.2010, 14:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  10.12.2010,  11:23 Найти цитируемый пост)
Или нужен сборщик мусора. Вроде как хотят его добавить в С++..

Цитата

Garbage collection: For C++0x, we're not going to add explicit support for garbage collection, and only intend to find ways to remove blocking issues like pointer hiding that make it difficult to add garbage collection in a C++ implementation. In particular, the scope of this feature is expected to be constrained as follows:

http://herbsutter.com/2007/11/01/trip-repo...ndards-meeting/
PM   Вверх
Леопольд
Дата 10.12.2010, 21:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  10.12.2010,  13:52 Найти цитируемый пост)
Почему же, в алгоритме, описанном в статье этой проблемы нет. 
Зато там довольно накладная очистка памяти, которая может свести на нет всю предполагаемую выгоду от lock free. В невытесняющей многозадачности, при определённой нагрузке, спин лок bудет гораздо bыстрее. Вот если bы везде bыла доступна связка LL/SC или DСAS, а лучше и то и другое и третье. 
Иначе это как программировать GUI имея в арсенале только bинарные операции. Ничего удивительного что для двусвязного списка не придумали алгоритм, недостаточно доступных средств. В оbщем, надо подождать... smile

Про сbорщик мусора, видимо, здесь на глаза попадалось.
http://www2.research.att.com/~bs/C++0xFAQ.html#gc-abi


Это сообщение отредактировал(а) Леопольд - 10.12.2010, 22:40


--------------------
вопросов больше чем ответов
PM MAIL   Вверх
azesmcar
Дата 10.12.2010, 22:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 81
Всего: 211



Цитата(Леопольд @  10.12.2010,  21:49 Найти цитируемый пост)
Зато там довольно накладная очистка памяти, которая может свести на нет всю предполагаемую выгоду от lock free

в твоем варианте тоже, вся проблема в том, что очистка происходит только тогда, когда в функции pop нет других потоков, что в случае высокой нагрузки маловероятно. Альтернатива есть, но решение не такое простое. Почитай у Maged Michael-а про Hazard Pointers.

Цитата(Леопольд @  10.12.2010,  21:49 Найти цитируемый пост)
Ничего удивительного что для двусвязного списка не придумали алгоритм, недостаточно доступных средств

все только начинается...


Это сообщение отредактировал(а) azesmcar - 10.12.2010, 23:16
PM   Вверх
Леопольд
Дата 11.12.2010, 09:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 943
Регистрация: 17.6.2009

Репутация: 10
Всего: 13



Цитата(azesmcar @  10.12.2010,  22:59 Найти цитируемый пост)
в твоем варианте тоже
Я пытался обойти тот момент, когда очередь полностью обходится только для того, что-бы поместить все элементы обратно. Если бы под рукой оказались ll/SC то могло получиться.



--------------------
вопросов больше чем ответов
PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1665 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.