Re[20]: Deadlock-prevention алгоритм
От: landerhigh Пират  
Дата: 29.06.14 09:14
Оценка:
Здравствуйте, xp1icit, Вы писали:

BFE>>Классический пример — перевод денег:


Классический, но академический. Ничего общего с тем, как это реально просходит, не имеет.

X>элементарно же:


Нет. В общем случае доступа ко второму счету нет. Это может быть счет в другом банке, другой системе и т.п. Заявка на перевод может требовать ручной обработки или промежуточного подтверждения.

1. Создается транзакция с параметрами "Перевод Х денег со счета А на счет Б".
2. Со счета А списывается Х денег. В зависимости от реализации они могут не списываться, а блокироваться.
3. Транзакция направляется по адресу. На обработку, проверку, в другой банк, в ФСБ и так далее.
4а. Система-адресат получает транзакцию, выполняет необходимые проверки, зачисляет деньги на счет и отправляет обратно подтверждение транзакции
4б. Система-адресат получает транзакцию, выполняет необходимые проверки, говорит "ЧТО ЭТО" и отправляет обратно отлуп.
4в. транзакция теряется по пути.
5а. Банк-отправитель получает подтверждение. В зависимости от реализации может блокированные средства перевести в списанные.
5б. Банк-отправитель получает подтверждение и возвращает деньги на счет.
5в. Спустя какое-то время взебешнный клиент начинает медленно плоскогубцами выдергивать волосы сотрудникам поддержки банка.
www.blinnov.com
Re[16]: Deadlock-prevention алгоритм
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 29.06.14 09:41
Оценка:
L>Имеет, т.к. пока непонятно, из каких требований отросло требование именно многопоточности. Любое решение должно быть обосновано, и часто бывает, что архитектурные решения принимаются из ошибочных исходных данных или ошибочных представлениях о требованиях.

Ну, например, клиент хочет купить более дорогой сервер и получить прирост, вместо уменьшения производительности. Вот купил он сервер с 4-мя зеонами у каждого из которых по 12 HT хардварных потока, запустил твой софт, в котором "число мьютексов строго соответствует колиеству месть взаимодействиям между потоками" и сервер начал вместо 100 000 rps выдавать 80 000 rps. А все потому что синхронизация теперь выполняется чаще (так как потоков стало больше, чтобы утилизировать более мощное железо) и любое использование мьютекса приводит к реальному взаимодействию между потоками (ибо их мало), но синхронизация у нас теперь выходит за границы сокета и все латентности выросли. Просто потому что у кого-то выработалась неправильная интуиция об этом во времена одноядерных машин, когда любая синхронизация шла в ядро и потоки всегда мешали друг другу, так как работали на одном ядре

Мне кажется, правило "число мьютексов строго соответствует колиеству месть взаимодействиям между потоками" в современном мире несколько иррелевантно. Во первых, следование этому правилу может усложнить код, в моем примере проще всего натыкать мьютексов и все. Если стараться следовать твоему правилу — код получится сложнее, вместо лока придется сигналить выделенному потоку или шардить данные между потоками. С точки зрения перфоманса это тоже не имеет большого смысла. Если я пишу какой-ниубдь сервер и у меня на каждый харварный поток приходится один софтверный поток, то в случае, если количество мьютексов равно количеству мест взаимодействиия потоков, захватывая мьютекс я практически гарантировано буду создавать contention, а это как раз то, что убивает перфоманс в параллельных системах. Относись к большому количеству мьютексов как к шардингу, только вместо данных ты шардишь мьютексы у тебя есть место взаимодействия между потоками и ему соответствует один мьютекс, ты берешь и реплицируешь его, чтобы уменьшить конкуренцию за отдельные мьютексы так, чтобы каждый отдельный мьютекс захватывался редко и чаще всего — только одним потоком, но корректность при этом не пострадала.

L>>А что если в одном запросе нужно обновлять сразу несколько счетчиков? Вот сразу и атомики не подходят и в том что мьютекс торчит наружу нет ничего плохого


L>А в чем проблема? Data continuity никаким образом не нарушается. Хочешь транзакций с изоляциями? А оно тебе надо? (не обижайся, я всегда такие вопросы задаю, в 90% случаев оказывается, что на самом деле не надо. Не следует пытаться сделать больше, чем требуется.) Если надо, то так бы сразу и сказал. Но это уже совсем другая история, не так ли? Я правильно понимаю, что ты хочешь захватить сразу все локи, и только потом обновлять? Ну так это же прямой путь к дедлоку, как только залетит дятел и порядок блокировки будет нарушен.


Никто не спорит с тем, что это очень часто не надо. Но я же делаю это для тех редких случаев когда это надо. Или мне лучше сразу репозиторий с гитхаба удалить?

L>>Простой пример — один горячий элемент, который обновляют все кому не лень. Есть такая задача в параллельном програмировании — зоопарк Шредингера, есть список животных живущих в зоопарке, клиенты могут запрашивать информацию о них, но почему то чаще всего запрашивают информацию о коте Шредингера


L>Видишь ли в чем дело. Ты пока не обосновал необходимость многопоточности. Если все, что твой сервис должен делать — это на запросы справляться о здоровье кота Шредингера, то вопрос о пропускной способности очереди клиентов нужно рассматривать первым. Учитывая имеющиеся входные данные, я пока вижу, что твой сервис в любом случае будет успевать отработать быстрее, нежели следующий запрос успеет прийти и быть обработанным сетевой подсистемой. Короче, один поток справится только в путь.


10g сейчас в каждом датацентре (это если мне память не изменяет 10 000 000 пакетов в секунду), обработать все что валится через обычный GbE может быть очень сложно, а еще на одном серваке может быть несколько сетевых интерфейсов, которые, в свою очередь, могут быть подключены к двум разным свитчам.
Re[11]: Deadlock-prevention алгоритм
От: sokel Россия  
Дата: 29.06.14 17:26
Оценка:
Здравствуйте, Lazin, Вы писали:

L>Правда так можно только deadlock первого порядка отловить, чтобы отловить deadlock N-ого порядка, нужно проверить N^2 таких переходов (если N это глубина стека вызовов).


thread-local можно стек и не собирать, просто последний заблокированный слой держать, атомарно отмечаем переход от слоя к слою.
И если такой переход случился впервые, синхронизируемся (чтобы все изменения observable были) и проверяем граф на циклы. Со всякими топологическими сортировками можно не заморачиваться, просто рекурсивно от ноды на которую переход пошёл пробежаться и проверить по глубине что все пути конечны (всё равно число слоев ограничили). Т.е. переход на предыдущую ноду использовать как признак зацикливания надо, но только на неё полагаться нельзя, т.к. могли быть другие циклы в параллель внесены. Тогда все потенциальные deadlock'и вылезти должны.
Re[12]: Deadlock-prevention алгоритм
От: sokel Россия  
Дата: 29.06.14 20:23
Оценка:
Здравствуйте, sokel, Вы писали:

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


L>>Правда так можно только deadlock первого порядка отловить, чтобы отловить deadlock N-ого порядка, нужно проверить N^2 таких переходов (если N это глубина стека вызовов).


S>thread-local можно стек и не собирать, просто последний заблокированный слой держать, атомарно отмечаем переход от слоя к слою.

S>И если такой переход случился впервые, синхронизируемся (чтобы все изменения observable были) и проверяем граф на циклы. Со всякими топологическими сортировками можно не заморачиваться, просто рекурсивно от ноды на которую переход пошёл пробежаться и проверить по глубине что все пути конечны (всё равно число слоев ограничили). Т.е. переход на предыдущую ноду использовать как признак зацикливания надо, но только на неё полагаться нельзя, т.к. могли быть другие циклы в параллель внесены. Тогда все потенциальные deadlock'и вылезти должны.


Как то так:
#include <stack>
#include <atomic>

#define MAX_LAYERS 100
class deadlock_detector
{
    deadlock_detector()
        : layer_count_()
        , deadlock_(false)
    {}
public:
    static deadlock_detector& get() {
        static deadlock_detector instance;
        return instance;
    }
    size_t register_layer(const char* name) {
        std::lock_guard<std::mutex> lock(mtx_);
        size_t ret = (++layer_count_) - 1;
        registered_layers_[ret] = name;
        return ret;
    }
    void on_lock(size_t i, bool enter) {
        __declspec(thread) static std::stack<size_t>* lock_stack = 0;
        if (lock_stack == 0)
            lock_stack = new std::stack<size_t>();
        if (enter) {
            if (!lock_stack->empty())
                on_transition(lock_stack->top(), i);
            lock_stack->push(i);
        }
        else {
            lock_stack->pop();
        }
    }
private:
    void on_transition(size_t i, size_t j) {
        size_t a, b;
        int trans;
        if (i < j) { a = i; b = j; trans = 1; }
        else { a = j; b = i; trans = 2; }
        std::atomic_int& edge = transitions_[a][b];
        if (edge == trans)
            return;
        int prev_trans = 0;
        std::atomic_compare_exchange_weak(&edge, &prev_trans, trans);
        std::lock_guard<std::mutex> lock(mtx_);
        if (!deadlock_) {
            if (prev_trans != trans || !check_cycle(j, i)) {
                deadlock_ = true;
                std::cout << "deadlock detected" << std::endl;
                print_transitions();
                //assert(false);
            }
        }
    }
    void print_transitions() {
        for (int j = 0; j < layer_count_; ++j) {
            for (int i = 0; i <= j; ++i) {
                int t = transitions_[i][j];
                if(!t) continue;
                int a = i, b = j;
                if (t != 1) std::swap(a, b);
                std::cout << registered_layers_[a] << "->" << registered_layers_[b] << std::endl;
            }
        }

    }
    bool check_cycle(int x, int init, int depth = 1) {
        if (depth > layer_count_) return true;
        if (x == init) return true;
        bool ret = false;
        int i = 0;
        while (!ret && i < x) {
            if (transitions_[i][x] == 2)
                ret = check_cycle(i, init, depth + 1);
            ++i;
        }
        if (transitions_[i][i]) return true;
        while (!ret && i < layer_count_) {
            if (transitions_[x][i] == 1)
                ret = check_cycle(i, init, depth + 1);
            ++i;
        }
        return ret;
    }
private:
    bool deadlock_;
    size_t layer_count_;
    std::mutex mtx_;
    const char* registered_layers_[MAX_LAYERS];
    std::atomic_int transitions_[MAX_LAYERS][MAX_LAYERS];
};
Re[13]: Deadlock-prevention алгоритм
От: sokel Россия  
Дата: 29.06.14 20:34
Оценка:
Здравствуйте, sokel, Вы писали:

Тут и атомики конечно не нужны, можно volatile обойтись.
Re[14]: Deadlock-prevention алгоритм
От: sokel Россия  
Дата: 29.06.14 20:47
Оценка:
Здравствуйте, sokel, Вы писали:

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


S>Тут и атомики конечно не нужны, можно volatile обойтись.


Тем более с атомиками ошибка....

Так вроде работает (просто volatile, без атомиков):
    void on_transition(size_t i, size_t j) {
        size_t a, b;
        int trans;
        if (i < j) { a = i; b = j; trans = 1; }
        else { a = j; b = i; trans = 2; }
        if (transitions_[a][b] == trans)
            return;
        std::lock_guard<std::mutex> lock(mtx_);
        if (transitions_[a][b] && transitions_[a][b] != trans)
            on_deadlock();
        else {
            transitions_[a][b] = trans;
            if (check_cycle(j, i))
                on_deadlock();
        }
    }
    void on_deadlock() {
        if (!deadlock_) {
            deadlock_ = true;
            std::cout << "deadlock detected" << std::endl;
            print_transitions();
        }
    }


тест:

syncope::SymmetricLockLayer l1(STATIC_STRING("layer 1"));
syncope::SymmetricLockLayer l2(STATIC_STRING("layer 2"));
syncope::SymmetricLockLayer l3(STATIC_STRING("layer 3"));

int64_t a[1000];

int main() {

    std::thread t1([]()
    {
        auto g1 = l1.synchronize(&a[0]);
        auto g2 = l2.synchronize(&a[0]);
    });
    std::thread t2([]()
    {
        auto g1 = l2.synchronize(&a[100]);
        auto g2 = l3.synchronize(&a[100]);
    });
    std::thread t3([]()
    {
        auto g1 = l3.synchronize(&a[200]);
        auto g2 = l1.synchronize(&a[200]);
    });

    t1.join();
    t2.join();
    t3.join();

    return 0;
}


Реального deadlock'a нет, блокируются независимо 0-1, 1-2, 2-3. Цикл определяет:
deadlock detected
layer 1->layer 2
layer 3->layer 1
layer 2->layer 3
Re[15]: Deadlock-prevention алгоритм
От: sokel Россия  
Дата: 29.06.14 21:47
Оценка:
Здравствуйте, sokel, Вы писали:

А раз атомимки не нужны, можно факт перехода вообще по простому битом отмечать. Единственно минус в том что когда все слои созданы надо initialize сказать.
#pragma once
#include <stack>
#include <vector>
#include <atomic>

class deadlock_detector
{
    deadlock_detector()
        : deadlock_(false)
    {}
public:
    static deadlock_detector& get() {
        static deadlock_detector instance;
        return instance;
    }
    size_t register_layer(const char* name) {
        std::lock_guard<std::mutex> lock(mtx_);
        assert(transitions_.empty());
        registered_layers_.push_back(name);
        return registered_layers_.size() - 1;
    }
    void initialize() {
        std::lock_guard<std::mutex> lock(mtx_);
        assert(transitions_.empty());
        transitions_.resize(registered_layers_.size() * registered_layers_.size());
    }
    void on_lock(size_t i, bool enter) {
        __declspec(thread) static std::stack<size_t>* lock_stack = 0;
        if (lock_stack == 0)
            lock_stack = new std::stack<size_t>();
        if (enter) {
            if (!lock_stack->empty())
                on_transition(lock_stack->top(), i);
            lock_stack->push(i);
        }
        else {
            lock_stack->pop();
        }
    }
private:
    void on_transition(size_t i, size_t j) {
        assert(!transitions_.empty());
        if (get_transition(i, j))
            return;
        std::lock_guard<std::mutex> lock(mtx_);
        add_transition(i, j);
        if (check_cycle(j, i))
            on_deadlock();
    }
    void on_deadlock() {
        if (!deadlock_) {
            deadlock_ = true;
            std::cout << "deadlock detected" << std::endl;
            print_transitions();
        }
    }
    void print_transitions() const {
        for (int i = 0; i < layer_count(); ++i) {
            for (int j = 0; j < layer_count(); ++j) {
                if (!get_transition(i, j)) continue;
                std::cout << registered_layers_[i] << "->" << registered_layers_[j] << std::endl;
            }
        }
    }
    bool check_cycle(int x, int init, int depth = 1) const {
        if (depth > layer_count() || x == init)
            return true;
        bool ret = false;
        for (int i = 0; i < layer_count() && !ret; ++i) {
            if (get_transition(x, i))
                ret = check_cycle(i, init, depth + 1);
        }
        return ret;
    }
    bool get_transition(size_t i, size_t j) const {
        return transitions_[i*layer_count() + j];
    }
    void add_transition(size_t i, size_t j) {
        transitions_[i*layer_count() + j] = true;
    }
    size_t layer_count() const {
        return registered_layers_.size();
    }
private:
    bool deadlock_;
    std::mutex mtx_;
    std::vector<const char*> registered_layers_;
    std::vector<bool> transitions_;
};
Re[17]: Deadlock-prevention алгоритм
От: landerhigh Пират  
Дата: 30.06.14 06:54
Оценка:
Здравствуйте, Lazin, Вы писали:

L>>Имеет, т.к. пока непонятно, из каких требований отросло требование именно многопоточности. Любое решение должно быть обосновано, и часто бывает, что архитектурные решения принимаются из ошибочных исходных данных или ошибочных представлениях о требованиях.


L>Ну, например, клиент хочет купить более дорогой сервер и получить прирост, вместо уменьшения производительности. Вот купил он сервер с 4-мя зеонами у каждого из которых по 12 HT хардварных потока, запустил твой софт, в котором "число мьютексов строго соответствует колиеству месть взаимодействиям между потоками" и сервер начал вместо 100 000 rps выдавать 80 000 rps.


Или вместо 100 попугаев — 150. А все потому, что под синхронизацией каждая операция проводит не более 1% операций, требуемых для обработки одного запроса и тупо добавление ядер позволило эффективно распараллелить остальные 99% операций.

L>Просто потому что у кого-то выработалась неправильная интуиция об этом во времена одноядерных машин, когда любая синхронизация шла в ядро и потоки всегда мешали друг другу, так как работали на одном ядре


Правильность или неправильность интуиции нельзя обсуждать в контексте сферического сервера в вакууме.

L>Если я пишу какой-ниубдь сервер и у меня на каждый харварный поток приходится один софтверный поток, то в случае, если количество мьютексов равно количеству мест взаимодействиия потоков, захватывая мьютекс я практически гарантировано буду создавать contention, а это как раз то, что убивает перфоманс в параллельных системах.


В твоем примере для создания дикой конкуренции достаточно, чтобы все клиенты стали в каждом запросе интересоваться здоровьем кота Шредингера. И никакого контроля за этим ты не имеешь.

L>Относись к большому количеству мьютексов как к шардингу, только вместо данных ты шардишь мьютексы у тебя есть место взаимодействия между потоками и ему соответствует один мьютекс, ты берешь и реплицируешь его, чтобы уменьшить конкуренцию за отдельные мьютексы так, чтобы каждый отдельный мьютекс захватывался редко и чаще всего — только одним потоком, но корректность при этом не пострадала.


И получаешь проблему, вынесенную в название этой темы? Спасибо, не надо. Лучше надежно работающий сервис, чем не работающий вообще. Сервис, допускающий дедлоки — нерабочий.

L>>>А что если в одном запросе нужно обновлять сразу несколько счетчиков? Вот сразу и атомики не подходят и в том что мьютекс торчит наружу нет ничего плохого


L>>А в чем проблема? Data continuity никаким образом не нарушается. Хочешь транзакций с изоляциями? А оно тебе надо? (не обижайся, я всегда такие вопросы задаю, в 90% случаев оказывается, что на самом деле не надо. Не следует пытаться сделать больше, чем требуется.) Если надо, то так бы сразу и сказал. Но это уже совсем другая история, не так ли? Я правильно понимаю, что ты хочешь захватить сразу все локи, и только потом обновлять? Ну так это же прямой путь к дедлоку, как только залетит дятел и порядок блокировки будет нарушен.


L>Никто не спорит с тем, что это очень часто не надо. Но я же делаю это для тех редких случаев когда это надо. Или мне лучше сразу репозиторий с гитхаба удалить?


Не знаю о каком репозитории ты говоришь, но тут ды допустил сразу несколько логических ошибок. Во-первых, у тебя есть куча совершенно независимых друг от друга данных (отдельный мьютекс на каждый индикатор на это намекает). Попытка последовательно блокировать все мьютексы перед досупом/апдейдом тебе не даст ровным счетом ничего. Никакой транзакционности. Ты не получишь ничего такого, чего не смог бы получить при помощи атомиков, зато устраиваешь себе свой любимый contention, да еще и с риском дедлока. С точки зрения клиента разницы в поведении не будет, т.к. что ровно в момент начала транзакции, что в любой момент после ее начала, может вмешаться другой поток и изменить значение N-ного элемента из запрошенного набора данных. Причем, пока этот второй поток этот элемент меняет, твой первый поток курит бамбук. Посему отказ от атомиков тут пока никак не обоснован.

А если ты хочешь транзакционности, то вот ее и нужно реализовывать, взяв за основу один из механизмов, используемых в СУБД, например. Только в случае блокировок блокировать блокировать сразу целые таблицы, а не отдельные записи. Ничего не напоминает?

L>10g сейчас в каждом датацентре (это если мне память не изменяет 10 000 000 пакетов в секунду),


Размер ethernet фрейма не фиксирован. Он лишь обычно ограничен сверху.

L>обработать все что валится через обычный GbE может быть очень сложно, а еще на одном серваке может быть несколько сетевых интерфейсов, которые, в свою очередь, могут быть подключены к двум разным свитчам.


Информация для размышления. Грубый подсчет на пальцах: 10g это примерно 1Гб/с за минусом накладных расходов. Если запрос имеет длину... ну 100 байт, ну возьмем 1000, то это дает миллион запросов в секунду. Что на порядок больше, чем в среднем получает гугль. Что-то очень часто народ интересуется здоровьем кота Шредингера
Короче, без требований к нагрузочной способности и выяснения, где у нас тут бутылочное горлышко, рассуждать о необходимости многопоточности сложно.
www.blinnov.com
Re[3]: Deadlock-prevention алгоритм
От: sokel Россия  
Дата: 30.06.14 07:25
Оценка:
Здравствуйте, Lazin, Вы писали:

Ещё вот такая штука — не лучше ли при расчёте хэшей завязываться на sizeof? Т.е. сейчас хэш по адресу считается с шагом адреса в 64 байта. Допустим у нас размер наиболее часто блокируемых объектов 128 байт, причем аллокируются объекты на страницах с выравниванием не меньше чем 128 байт. Т.о. из всего набора мьютексов для них будет использоваться только половина. При размере 256 байт будет использоваться только четверть мьютексов, ну и так далее.

Такой хэш будет давать лучшее распределение блокировок:
        //! Simple hash - simply returns it's argument
        struct SimpleHash {
            template<typename T>
            size_t operator() (const T* value) const {
                return reinterpret_cast<size_t>(value)/sizeof(T);
            }
        };
Re[4]: Deadlock-prevention алгоритм
От: sokel Россия  
Дата: 30.06.14 07:32
Оценка:
Здравствуйте, sokel, Вы писали:

А, ну и мало того, маскирование по размеру пула нужно тоже в хэш функцию вынести, иначе unique бесполезен.

        //! Simple hash - simply returns it's argument
        struct SimpleHash {
            template<typename T>
            size_t operator() (const T* value) const {
                return (reinterpret_cast<size_t>(value) / sizeof(T))&LockLayerImpl::MASK;
            }
        };
Re[18]: Deadlock-prevention алгоритм
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 30.06.14 07:48
Оценка:
Я, честно говоря, задолбался уже спорить о сферических конях в вакууме, если бы не нужно было сидеть и ждать эвакуатор целый час...

L>>Ну, например, клиент хочет купить более дорогой сервер и получить прирост, вместо уменьшения производительности. Вот купил он сервер с 4-мя зеонами у каждого из которых по 12 HT хардварных потока, запустил твой софт, в котором "число мьютексов строго соответствует колиеству месть взаимодействиям между потоками" и сервер начал вместо 100 000 rps выдавать 80 000 rps.

L>Или вместо 100 попугаев — 150. А все потому, что под синхронизацией каждая операция проводит не более 1% операций, требуемых для обработки одного запроса и тупо добавление ядер позволило эффективно распараллелить остальные 99% операций.

Ну так а как они распараллелятся, если мьютекс один (твой идеальный случай, идеальнее некуда — одно место взаимодействия потоков с одним мьютексом в серединке).

L>Правильность или неправильность интуиции нельзя обсуждать в контексте сферического сервера в вакууме.


Я, собственно, для этого конкретный пример и предложил.

L>>Если я пишу какой-ниубдь сервер и у меня на каждый харварный поток приходится один софтверный поток, то в случае, если количество мьютексов равно количеству мест взаимодействиия потоков, захватывая мьютекс я практически гарантировано буду создавать contention, а это как раз то, что убивает перфоманс в параллельных системах.

L>В твоем примере для создания дикой конкуренции достаточно, чтобы все клиенты стали в каждом запросе интересоваться здоровьем кота Шредингера. И никакого контроля за этим ты не имеешь.

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

L>>Относись к большому количеству мьютексов как к шардингу, только вместо данных ты шардишь мьютексы у тебя есть место взаимодействия между потоками и ему соответствует один мьютекс, ты берешь и реплицируешь его, чтобы уменьшить конкуренцию за отдельные мьютексы так, чтобы каждый отдельный мьютекс захватывался редко и чаще всего — только одним потоком, но корректность при этом не пострадала.

L>И получаешь проблему, вынесенную в название этой темы? Спасибо, не надо. Лучше надежно работающий сервис, чем не работающий вообще. Сервис, допускающий дедлоки — нерабочий.

Если голова на месте, то не получаешь. И каким образом из того что я написал следует то, что сервис допускает дедлоки?

L>>>>А что если в одном запросе нужно обновлять сразу несколько счетчиков? Вот сразу и атомики не подходят и в том что мьютекс торчит наружу нет ничего плохого


L>>>А в чем проблема? Data continuity никаким образом не нарушается. Хочешь транзакций с изоляциями? А оно тебе надо? (не обижайся, я всегда такие вопросы задаю, в 90% случаев оказывается, что на самом деле не надо. Не следует пытаться сделать больше, чем требуется.) Если надо, то так бы сразу и сказал. Но это уже совсем другая история, не так ли? Я правильно понимаю, что ты хочешь захватить сразу все локи, и только потом обновлять? Ну так это же прямой путь к дедлоку, как только залетит дятел и порядок блокировки будет нарушен.


L>>Никто не спорит с тем, что это очень часто не надо. Но я же делаю это для тех редких случаев когда это надо. Или мне лучше сразу репозиторий с гитхаба удалить?


L>Не знаю о каком репозитории ты говоришь, но тут ды допустил сразу несколько логических ошибок. Во-первых, у тебя есть куча совершенно независимых друг от друга данных (отдельный мьютекс на каждый индикатор на это намекает). Попытка последовательно блокировать все мьютексы перед досупом/апдейдом тебе не даст ровным счетом ничего. Никакой транзакционности. Ты не получишь ничего такого, чего не смог бы получить при помощи атомиков, зато устраиваешь себе свой любимый contention, да еще и с риском дедлока. С точки зрения клиента разницы в поведении не будет, т.к. что ровно в момент начала транзакции, что в любой момент после ее начала, может вмешаться другой поток и изменить значение N-ного элемента из запрошенного набора данных. Причем, пока этот второй поток этот элемент меняет, твой первый поток курит бамбук. Посему отказ от атомиков тут пока никак не обоснован.


Я говорю о моем репозитории с кодом из первого поста. Если все это тлен, как ты говоришь, то зря я парюсь, нужно удалить его и все. Eine Frau, ein Loch, eine mutex!
Итак, атомики — не нужны, ибо мне может потребоваться не только складывать числа (это вообще высосано из пальца для иллюстрации идеи). Это может быть регистрация показания датчика или click stream от пользователя сайта. Помимо этого, атомики имеют примерно ту же стоимость, что и локи (если речь идет о спин локе), атомик за который есть конкуренция потоков будет медленным (RMW операция за такую переменную будет стоить сотен тактов, как и в случае спинлока), так что атомики на параллельную архитектуру влияют мало. Один хрен нужно ограничивать конкуренцию потоков (aka contention). Ну и если ты не в курсе — захват локов в одном, глобавльном, фиксированном порядке гарантирует целостность данных (если ты конечно захватил все локи) и отсутствие дедлоков.

L>А если ты хочешь транзакционности, то вот ее и нужно реализовывать, взяв за основу один из механизмов, используемых в СУБД, например. Только в случае блокировок блокировать блокировать сразу целые таблицы, а не отдельные записи. Ничего не напоминает?


Зависит от СУБД, в постгресе вон вообще без локов таблиц обходоится обычно.

L>Информация для размышления. Грубый подсчет на пальцах: 10g это примерно 1Гб/с за минусом накладных расходов. Если запрос имеет длину... ну 100 байт, ну возьмем 1000, то это дает миллион запросов в секунду. Что на порядок больше, чем в среднем получает гугль. Что-то очень часто народ интересуется здоровьем кота Шредингера

L>Короче, без требований к нагрузочной способности и выяснения, где у нас тут бутылочное горлышко, рассуждать о необходимости многопоточности сложно.

Есть задачи где на один сервер может валиться больше. Статистика гугла показывает запросы пользователей. Получается, что на предыдущей работе у нас сервер, занимающийся сбором time-series данных от сенсоров обрабатывал больше запросов на запись в секунду, чем весь гугл? А может ты просто теплое с мягким сравниваешь? Я могу сказать с уверенностью, что всякие time-series данные от всяких сенсоров, клик стримы (и вообще обработка стримов), RTB, баннерокрутилки и тд и тп — легко могут требовать дикой конкурентности и выдают как правило 100500 запросов в сек и больше (буквально).
Re[19]: Deadlock-prevention алгоритм
От: landerhigh Пират  
Дата: 30.06.14 08:49
Оценка:
Здравствуйте, Lazin, Вы писали:

L>Я, честно говоря, задолбался уже спорить о сферических конях в вакууме, если бы не нужно было сидеть и ждать эвакуатор целый час...


Зачем ты ее поломал?

L>>Или вместо 100 попугаев — 150. А все потому, что под синхронизацией каждая операция проводит не более 1% операций, требуемых для обработки одного запроса и тупо добавление ядер позволило эффективно распараллелить остальные 99% операций.


L>Ну так а как они распараллелятся, если мьютекс один (твой идеальный случай, идеальнее некуда — одно место взаимодействия потоков с одним мьютексом в серединке).


99% процентов времени каждый поток занят своим делом. Нормальное такое распараллелирвание. Будут, конечено, время от времени возникать коллизии, но ничего критичного не произойдет.

L>>В твоем примере для создания дикой конкуренции достаточно, чтобы все клиенты стали в каждом запросе интересоваться здоровьем кота Шредингера. И никакого контроля за этим ты не имеешь.


L>Ну так остальные запросы простаивать то тоже не будут, они будут обслуживаться другими ядрами, так как запрос может обработать любое ядро.


А остальных запросов-то и нет. Всем нужен кот.

L>>И получаешь проблему, вынесенную в название этой темы? Спасибо, не надо. Лучше надежно работающий сервис, чем не работающий вообще. Сервис, допускающий дедлоки — нерабочий.


L>Если голова на месте, то не получаешь. И каким образом из того что я написал следует то, что сервис допускает дедлоки?


Ну так а проблема-то откуда-то ведь выросла?

L>>>>>А что если в одном запросе нужно обновлять сразу несколько счетчиков? Вот сразу и атомики не подходят и в том что мьютекс торчит наружу нет ничего плохого


L>Я говорю о моем репозитории с кодом из первого поста. Если все это тлен, как ты говоришь, то зря я парюсь, нужно удалить его и все. Eine Frau, ein Loch, eine mutex!


Речь не об одном мьютексе, а об архитектуре. И таки да, я буду продолжать утрвеждать, что детекторы дедлоков имеют мало смысла с точки зрения верификации ПО. Если архитектура настолько ущербна, что допускает дедлоки, то ничего, кроме ее переработки, не поможет. Детектор может помочь выявить и даже несколько дедлоков, но он не способен гарантировать отсутствие других, не обнаруженных пока, локов. Как инструмент раскопок в говнокоде, который нужно заставить хоть как-то работать, их использовать можно. Вопрос только, нужно ли?

L>Итак, атомики — не нужны, ибо мне может потребоваться не только складывать числа (это вообще высосано из пальца для иллюстрации идеи). Это может быть регистрация показания датчика или click stream от пользователя сайта. Помимо этого, атомики имеют примерно ту же стоимость, что и локи (если речь идет о спин локе), атомик за который есть конкуренция потоков будет медленным (RMW операция за такую переменную будет стоить сотен тактов, как и в случае спинлока), так что атомики на параллельную архитектуру влияют мало.


Они как минимум убирают необходимость в 100500 явных блокировок.

L>Один хрен нужно ограничивать конкуренцию потоков (aka contention). Ну и если ты не в курсе — захват локов в одном, глобавльном, фиксированном порядке гарантирует целостность данных (если ты конечно захватил все локи) и отсутствие дедлоков.


Это я-то не в курсе?

L>>А если ты хочешь транзакционности, то вот ее и нужно реализовывать, взяв за основу один из механизмов, используемых в СУБД, например. Только в случае блокировок блокировать блокировать сразу целые таблицы, а не отдельные записи. Ничего не напоминает?


L>Зависит от СУБД, в постгресе вон вообще без локов таблиц обходоится обычно.


Потому что постгрес версионник. За это, кстати, приходится расплачиваться переодическим vacuum.

L>Есть задачи где на один сервер может валиться больше. Статистика гугла показывает запросы пользователей. Получается, что на предыдущей работе у нас сервер, занимающийся сбором time-series данных от сенсоров обрабатывал больше запросов на запись в секунду, чем весь гугл? А может ты просто теплое с мягким сравниваешь? Я могу сказать с уверенностью, что всякие time-series данные от всяких сенсоров,


Ты кагбы можешь мне это не рассказывать, для меня IEC61850, MODBUS, BACNET, DNP3 и прочие страшные слова — вовсе не пустой звук. Но мы подобных проблем почему-то не имеем.
www.blinnov.com
Re[20]: Deadlock-prevention алгоритм
От: B0FEE664  
Дата: 30.06.14 09:14
Оценка:
Здравствуйте, xp1icit, Вы писали:

X>элементарно же:

X>1) лочим оба счета в однозначно определяемом порядке (типа сначала с меньшим номером и т.п.)
X>2) переводим деньги
X>3) разлочиваем в обратном порядке
X>эту странную ситуацию — когда двум тредам нужно залочить два объекта ну обязательно в разном порядке — вряд ли невозможно подобным простым образом исключить

Собственно, этот метод, но с группированием мютексов по уровням и предлагается делать в статье, на базе которой Lazin написал библиотеку.

С чем спорит landerhigh — мне не понятно.
И каждый день — без права на ошибку...
Re[21]: Deadlock-prevention алгоритм
От: landerhigh Пират  
Дата: 30.06.14 09:16
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Собственно, этот метод, но с группированием мютексов по уровням и предлагается делать в статье, на базе которой Lazin написал библиотеку.


BFE>С чем спорит landerhigh — мне не понятно.


Не спорит, а утверждает, что группирование мьютексов по уровням — это уже само по себе заметание проблемы под ковер.
www.blinnov.com
Re[22]: Deadlock-prevention алгоритм
От: B0FEE664  
Дата: 30.06.14 09:18
Оценка:
Здравствуйте, landerhigh, Вы писали:

L>>>Поздно уже предотвращать. Система в продакшене. Дедлок выстрелил, заслонка зависла в открытом положении, самолет упал, электростанция взорвалась.

BFE>>Можно предотвратить заранее. Цена — не оптимальный алгоритм.
L>Нельзя. Если система заходит в дедлок, то боржоми пить уже поздно.
Я же говорю, можно предотвратить заранее, до захода в дедлок.

BFE>>Чем это ситуация отличается от брошенного исключения? Файл уже удален, заслонка открыта, газ идет...


L>Примерно всем. Исключение понятно как обработать. А дедлок? Что с ним делать?

Обнаружили дедлок — бросили исключение. А как обработать исключение — понятно. В чём проблема-то?

BFE>>>>Если мы применяем RAII, то можно бросить исключение и поймать его выше всех локов, после чего повторить попытку.

L>>>Единственный более-менее приемлимый вариант — упасть с дампом. Что тоже очень плохо, но лучше зависания.
BFE>>Упасть — это не вариант.
L>А заклинившая педаль газа, упавший самолет и взорвавшийся реактор — вариант?
Я же говорю — не вариант.
И каждый день — без права на ошибку...
Re[22]: Deadlock-prevention алгоритм
От: B0FEE664  
Дата: 30.06.14 09:28
Оценка:
Здравствуйте, landerhigh, Вы писали:

BFE>>Собственно, этот метод, но с группированием мютексов по уровням и предлагается делать в статье, на базе которой Lazin написал библиотеку.

BFE>>С чем спорит landerhigh — мне не понятно.
L>Не спорит, а утверждает, что группирование мьютексов по уровням — это уже само по себе заметание проблемы под ковер.

То решение, что вы предлагаете, существенно проигрывает по скорости для некоторых задач.
И каждый день — без права на ошибку...
Re[23]: Deadlock-prevention алгоритм
От: landerhigh Пират  
Дата: 30.06.14 09:50
Оценка:
Здравствуйте, B0FEE664, Вы писали:

L>>Нельзя. Если система заходит в дедлок, то боржоми пить уже поздно.

BFE>Я же говорю, можно предотвратить заранее, до захода в дедлок.

Обнаружить дедлок до захода в дедлок невозможно. То есть когда твой детектор сработал, предотвращать уже поздно.

BFE>>>Чем это ситуация отличается от брошенного исключения? Файл уже удален, заслонка открыта, газ идет...


L>>Примерно всем. Исключение понятно как обработать. А дедлок? Что с ним делать?

BFE>Обнаружили дедлок — бросили исключение. А как обработать исключение — понятно. В чём проблема-то?

Дедлок — по определению непредусмотренное поведение программы. Ну бросил ты исключение? Что ты с ним делать будешь, ты ж не предполагал, что дедлок может возникнуть? Или ты уже предполагаешь, что дедлоки могут возникнуть и уже написал класс DeadLockException? Тогда лучше потратить это время с бОльшей пользой и таки исправить поведение программы, чтобы исключить саму возможность дедлока.

BFE>>>>>Если мы применяем RAII, то можно бросить исключение и поймать его выше всех локов, после чего повторить попытку.

L>>>>Единственный более-менее приемлимый вариант — упасть с дампом. Что тоже очень плохо, но лучше зависания.
BFE>>>Упасть — это не вариант.
L>>А заклинившая педаль газа, упавший самолет и взорвавшийся реактор — вариант?
BFE>Я же говорю — не вариант.

Тогда нужно проектировать правильно, без вариантов. В общем случае ты отктаться не сможешь.
www.blinnov.com
Re[23]: Deadlock-prevention алгоритм
От: landerhigh Пират  
Дата: 30.06.14 09:52
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>>>С чем спорит landerhigh — мне не понятно.


L>>Не спорит, а утверждает, что группирование мьютексов по уровням — это уже само по себе заметание проблемы под ковер.


BFE>То решение, что вы предлагаете, существенно проигрывает по скорости для некоторых задач.


Пока что мой опыт показывает, что попытка выиграть в скорости за счет игнорирования проблемы дедлоков приводит к проигрышу по всем фронтам без исключения.
Никому не нужно "быстрое решение", которое "иногда" лочится. Относительно "медленное", но работающее корректно всегда предпочтительнее.
www.blinnov.com
Re[24]: Deadlock-prevention алгоритм
От: B0FEE664  
Дата: 30.06.14 10:35
Оценка:
Здравствуйте, landerhigh, Вы писали:

L>>>Нельзя. Если система заходит в дедлок, то боржоми пить уже поздно.

BFE>>Я же говорю, можно предотвратить заранее, до захода в дедлок.
L>Обнаружить дедлок до захода в дедлок невозможно. То есть когда твой детектор сработал, предотвращать уже поздно.
А я не об этом говорю.

BFE>>>>Чем это ситуация отличается от брошенного исключения? Файл уже удален, заслонка открыта, газ идет...

L>>>Примерно всем. Исключение понятно как обработать. А дедлок? Что с ним делать?
BFE>>Обнаружили дедлок — бросили исключение. А как обработать исключение — понятно. В чём проблема-то?

L>Дедлок — по определению непредусмотренное поведение программы.

Не более, чем исключение.
L>Ну бросил ты исключение? Что ты с ним делать будешь, ты ж не предполагал, что дедлок может возникнуть?
Зато я предпологал, что может быть исключение.

L>Или ты уже предполагаешь, что дедлоки могут возникнуть и уже написал класс DeadLockException?

Как вариант.

L>Тогда лучше потратить это время с бОльшей пользой и таки исправить поведение программы, чтобы исключить саму возможность дедлока.

А где обоснование этого утверждения? Предполагать исключение придётся в любом случае, а значит обработка исключений должна быть в любом случае.

BFE>>>>>>Если мы применяем RAII, то можно бросить исключение и поймать его выше всех локов, после чего повторить попытку.

L>>>>>Единственный более-менее приемлимый вариант — упасть с дампом. Что тоже очень плохо, но лучше зависания.
BFE>>>>Упасть — это не вариант.
L>>>А заклинившая педаль газа, упавший самолет и взорвавшийся реактор — вариант?
BFE>>Я же говорю — не вариант.
L>Тогда нужно проектировать правильно, без вариантов. В общем случае ты отктаться не сможешь.
В общем случае много чего невозможно.
И каждый день — без права на ошибку...
Re[25]: Deadlock-prevention алгоритм
От: landerhigh Пират  
Дата: 30.06.14 11:10
Оценка: 15 (1) +1
Здравствуйте, B0FEE664, Вы писали:

L>>Обнаружить дедлок до захода в дедлок невозможно. То есть когда твой детектор сработал, предотвращать уже поздно.

BFE>А я не об этом говорю.

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

BFE>>>>>Чем это ситуация отличается от брошенного исключения? Файл уже удален, заслонка открыта, газ идет...

L>>>>Примерно всем. Исключение понятно как обработать. А дедлок? Что с ним делать?
BFE>>>Обнаружили дедлок — бросили исключение. А как обработать исключение — понятно. В чём проблема-то?

Исключение — это исключительная ситуация, которая, однако, ожидаема и может быть исправлена. Некоторые ковбои пытаются ловить и "обрабатывать" и неисправимые ситуации — вроде конца света памяти или 0x5 (под виндой), но это неправильно, т.к. исправить ситуацию уже нельзя.
Дедлок по определению ситуация не ожидаемая. Если она ожидается, то ее всегда можно исправить в коде и полностью исключить.

L>>Дедлок — по определению непредусмотренное поведение программы.

BFE>Не более, чем исключение.

О майн готт. Не знаю, смеяться или плакать.

L>>Ну бросил ты исключение? Что ты с ним делать будешь, ты ж не предполагал, что дедлок может возникнуть?

BFE>Зато я предпологал, что может быть исключение.
L>>Или ты уже предполагаешь, что дедлоки могут возникнуть и уже написал класс DeadLockException?
BFE>Как вариант.

И что ты будешь с ним делать?

L>>Тогда лучше потратить это время с бОльшей пользой и таки исправить поведение программы, чтобы исключить саму возможность дедлока.

BFE>А где обоснование этого утверждения? Предполагать исключение придётся в любом случае, а значит обработка исключений должна быть в любом случае.

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

L>>Тогда нужно проектировать правильно, без вариантов. В общем случае ты отктаться не сможешь.

BFE>В общем случае много чего невозможно.

Проектировать правильно — возможно.
www.blinnov.com
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.