1. C++ / Говнокод #17579

    +136

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    #include <stdint.h>
    #include <Windows.h>
    #include <intrin.h>
    
    typedef long dt;
    
    namespace {
    	dt InitializationIsInProgress = 0;
    	dt InitializationIsFinished = 0;
    }
    
    dt InterlockedLoad(dt volatile * t){
    	return InterlockedCompareExchange(t, 0, 0);
    }
    
    dt InterlockedAssign(dt volatile * t, dt v){
    	dt c = 0;
    	while (c = InterlockedCompareExchange(t, v, c));
    }
    
    void InitializeSystem(){
    	if (InterlockedLoad(&InitializationIsFinished) == 1)
    		return;
    	while (InterlockedCompareExchange(&InitializationIsInProgress, 1, 0) == 1) Sleep(0);
    	//__ReadWriteBarrier();
    	if (InterlockedLoad(&InitializationIsFinished) == 1)
    		return;
    	Work();
    	InterlockedAssign(&InitializationIsFinished, 1);
    	InterlockedAssign(&InitializationIsInProgress, 0);
    }

    Не судите строга. Воспользуюсь как пастебином. Если найдете ошибки - пришлю пирожок.

    Запостил: LispGovno, 04 Февраля 2015

    Комментарии (42) RSS

    • ко ко ко
      Ответить
    • А-а-а, парни, пожалуйста, подскажите, что прочитать на тему атомиков; вот так, чтоб осознать происходящее в процессоре на уровне инструкций? Что находил - либо академический матан, либо документация к стд::атомик, от которой без понимания внутренностей толку - ноль. Лучшее, что нашел - 1024cores, но оно не систематизировано и местами описано поверхностно.
      Ответить
    • Кстати в InterlockedAssign ошибка. == v забыл в условии
      Ответить
    • > InterlockedCompareExchange(t, 0, 0);
      Помещаем в переменную 0 если в ней был ноль? Вот зе фак?
      Ответить
      • А как ты себе представляешь сделать чтение переменной интерлоканной по-другому?
        Ответить
        • std::call_once() делаешь?
          Ответить
        • то ли в бустах, то ли в кутэ делали адд с нулем и возвращали результат.
          Ответить
          • lock mov недостаточно?
            Ответить
            • это ассемблер. я туда не полезу. он только для школьников. не кросплатформенный. ну и компилятор тоже должен знать (барьеры компилятора). ну и барьеры памяти тоже нужны.
              Ответить
              • Ну вот с этим жопа, да. Ну тогда придётся платить излишним для этой задачи CAS'ом, раз либу с атомиками подключить нельзя, а под каждую платформу писать асм - тем более.

                Цикл можно убрать, емнип, только поставив какой-нить wait condition (event в winapi) но там начинается проблема с тем, кто будет его запиливать...
                Ответить
              • // NotInitialized = 0, InProgress = 1, Initialized = 2
                void InitializeSystem() {
                    int state = InterlockedCompareExchange(InitializationState, InProgress, NotInitialized);
                    if (state == 2) {
                        // другой тред сделал всё за нас
                        return;
                    }
                    if (state == 1) {
                        // другой тред выполняет инициализацию
                        // вот сюда можно WaitForSingleObject
                        while (InterlockedCompareExchange(InitializationState, InProgress, InProgress) == InProgress) {
                            Yield();
                        }
                        return;
                    }
                    Work();
                    InterlockedCompareExchange(InitializationState, Initialized, InProgress);
                    // а сюда - SetEvent
                }
                Ответить
                • P.S. InterlockedAssign() тут, имхо, совершенно лишняя абстракция - хуй запилишь в данном базисе (цикл и т.п.), а толку никакого, мы ведь старое значение и так знаем.
                  Ответить
                • Умный борманд... Неужели интерлокедасайгн если требуется, то я написал говнокод и потому в мире атомиков его нет... А казалось бы пишется в одну инструкцию. (
                  Ответить
                  • > то я написал говнокод
                    Я только про данный случай.

                    atomic_store() аля InterlockedAssign(), емнип, просто запись в правильно выровненную переменную + фенс нужного типа (если relaxed - то просто запись безо всяких фенсов)...
                    Ответить
                    • Релакседы всякие редко нужны. Из-за подобных оптимизаций головного мозга мы можем и гонки получить, друг....
                      Ответить
                  • With Visual Studio 2003, volatile to volatile references are ordered; the compiler will not re-order volatile variable access. With Visual Studio 2005, the compiler also uses acquire semantics for read operations on volatile variables and release semantics for write operations on volatile variables (when supported by the CPU).

                    Т.е. под msvc можно сделать переменную со статусом volatile и правильно выровнять её. Тогда самая обычная запись в неё будет atomic_store с release семантикой. А самое обычное чтение - atomic_load с aquire семантикой.
                    volatile long InitializationState; // не умею выравнивать под вижуалкой, но скорее всего сама справится
                    
                    void InitializeSystem() {
                        if (InitializationState == Initialized) {
                            // другой тред всё сделал
                            // aquire семантика на чтении InitializationState гарантирует нам
                            // что мы увидим все результаты его работы
                            return;
                        }
                        if (InterlockedCompareExchange(&InitializationState, InProgress, NotInitialized) == InProgress) {
                            // другой тред работает над инициализацией, ждём его
                            while (InitializationState != Initialized)
                                Yield();
                            // как и в прошлом случае мы видим все результаты
                            return;
                        }
                        Work();
                        InitializationState = Initialized;
                        // release семантика на записи InitializationState гарантирует, что
                        // все треды, сделавшие чтение с aquire семантикой увидят
                        // то, что наделали в Work
                    }
                    Должно работать даже на ARM'ах с вендой.
                    Ответить
                    • P.S. Но Тарас с его любимой 2003 студией тут пролетает. Ему придётся втыкать MemoryBarrier() перед чтением и после записи (если я правильно понимаю).
                      Ответить
                      • Почему?
                        Ответить
                        • Потому что 2003 гарантирует только что-то типа relaxed'a (если я правильно понимаю).
                          Ответить
                        • P.S. Походу, даже relaxed'а нету. Про 2003 попалась цитатка, что гарантируется только порядок между volatile и volatile на уровне компилятора. А для проца никаких инструкций не генерится.
                          Ответить
                      • в шарпике есть "полубарьеры", Thread.VolatileRead() и Thread.VolatileWrite()
                        а есть и классический Thread.MemoryBarrier().
                        из разряда - "очень занимательно но нахуй не нужно"
                        Ответить
                        • В этих ваших шарпиках и жабах всё слишком просто... У них memory model чётко расписана (хотя у жабы она только где-то в районе 1.5-1.6 адекватной стала). А в крестах тредов не было до 2011 года, как и секса в ссср. Поэтому тут треш угар и содомия.
                          Ответить
                          • А откуда брались дети многопоточные приложения?
                            Ответить
          • Меня больше волнует как InterlockedAssign делать, так как у меня какая-то жесть с циклом. Тормозно получится
            Ответить
        • Ну я понял твою мысль. Ты этот InterlockedCompareExchange не ради атомарности туда засунул, а ради ordering'а и видимости. Для атомарности хватило бы правильно выровненной переменной размером в машинное слово.
          Ответить
          • Что-за видимость бро? ордеринг разве не включает это понятие?
            Ответить
            • В принципе, он и есть. Чтобы между store и load на эту переменную был ordering.
              Ответить
          • Кстати небось не в машинное слово надо выравнивать, а на границу кеш линии, типа

            __declspec(align(32)) dt InitializationIsInProgress = 0;
            __declspec(align(32)) dt InitializationIsFinished = 0;
            Ответить
            • Ну MSDN в справке по интерлокедам говорит, что достаточно на длину самого слова. Мы же только про атомарность: кешлайн либо выгрузят либо нет, другое ядро кешлайн либо загрузит, либо нет, в любом случае переменную на части не распидорасит, будет либо старое значение, либо новое, и атомарность не нарушается.

              На кешлайн равняют чтобы поднять производительность для независимых переменных.
              Ответить
    • Вызови InitializeSystem() из main() до старта тредов. И проблема решена. Запустится ровно один раз, на 146% потокобезопасно, не требует абсолютно ничего от компилятора. Все эти ленивые инициализации - горе от ума.
      Ответить
      • Ну херзнает. Это нифига не модно. Вдруг захочу юзать из конструктора статических переменных и там же в статических переменных у меня будет создаваться поток
        Ответить

    Добавить комментарий