1. Си / Говнокод #12016

    +140

    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    int next_id() {
        static int id = 0;
        __sync_add_and_fetch(&id, 1);
        return id;
    }

    "Атомарная" раздача айдишек.

    Запостил: bormand, 28 Октября 2012

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

    • Поясните человеку, не знакомому с многопоточным программированием, в чем здесь беда.

      P.S. А, пардон, дошло. Между третьей и четвертой строчкой может произойти что угодно.
      Ответить
      • > Между третьей и четвертой строчкой может произойти что угодно.
        Да, все верно, для интереса проводил эксперимент - 5-10% полученных айдишек совпадают. Правильный код был бы:
        return __sync_add_and_fetch(&id, 1);
        Ответить
        • pthread_mutex_lock(&rand_mutex);
          __sync_add_and_fetch(&id, 1);
          pthread_mutex_unlock(&rand_mutex);
          return id;
          Ответить
          • Ага, можно еще десяток семафоров навесить вокруг этой строки для верности. Жаль, что не поможет ;)
            Ответить
          • Тонко
            Ответить
          • Почти баян:
            > Между третьей и четвертой строчкой может произойти что угодно.

            А если так?
            int tmp; 
            pthread_mutex_lock(&rand_mutex);
            tmp = __sync_add_and_fetch(&id, 1);
            pthread_mutex_unlock(&rand_mutex);
            return tmp;
            Ответить
            • А так нормально, но мьютекс тут не нужен.
              Ответить
              • Он тут нужен исключительно для выразительности, чтобы всех убедить, что код безопасен. Ведь мужики же не поверят, что всё работает, если его не увидят.
                Ответить
                • void enter_critical_section() {
                      // do nothing
                  }
                  void leave_critical_section() {
                      // do nothing
                  }
                  int get_id() {
                      static int id = 0;
                      int tmp;
                      enter_critical_section();
                      tmp = __sync_add_and_fetch(&id, 1);
                      leave_critical_section();
                      return tmp;
                  }
                  Ответить
                  • Да, так лучше.
                    Ответить
                    • А теперь приходит Вася, и оптимизирует код:
                      int get_id() {
                          static int id = 0;
                          int tmp;
                          enter_critical_section();
                          // тут стояла неведомая хуйня, но я ее убрал
                          tmp = ++id;
                          leave_critical_section();
                          return tmp;
                      }
                      Ответить
                      • А сам по себе ++id для волатильного потокообщего id разве не в атомарный add компилируется?
                        Ответить
                        • Не факт.
                          Cмысл volatile немного другой - отключение оптимизации.
                          А вот CAS даст 100% гарантию.
                          Ответить
                        • Ну хотя тебе как пользователю VS
                          http://msdn.microsoft.com/en-us/library/12a04hfd(VS.80).aspx
                          volatile будет гарантировать атомность. Вот суки, приучают же людей

                          ЕМНИП даже для bool нет никаких гарантий. Только лочка или CAS.
                          Ответить
                          • > volatile будет гарантировать атомность
                            Атомарность чтения и записи - да. В том смысле, что половина переменной не запишется... Тут их выпендреж разве что в барьерах около чтений\записей в volatile переменные.

                            А вот инкремент всяко превратится в честное [загрузить - увеличить - сохранить].
                            Ответить
                        • надо читать новый стандарт, старый же никаких многопоточностей не предполагал
                          Ответить
                        • >для волатильного потокообщего id разве не в атомарный add компилируется?
                          Это зависит от многих факторов. Может на твоём селероне оно и будет, а на другой машине - нет. Я уверен можно даже написать пример, который будет это демонстрировать:
                          Вот смотри. long, который в памяти - 64 бита, на 32-битной машине оно как за одно действие?

                          >старый же никаких многопоточностей не предполагал
                          Лолшто? А зачем тогда вообще volatile ввели?
                          Ответить
                          • > Вот смотри. long, который в памяти - 64 бита, на 32-битной машине оно как за одно действие?

                            Компилировать в говно с секциями.
                            Ответить
                            • >Компилировать в говно с секциями.
                              О чем ты?
                              Ответить
                            • > Компилировать в говно с секциями.
                              Ну а __sync_* примерно так и работают. Если платформа позволяет - компилятор пытается описать их одной специальной инструкцией, если не позволяет - через говнище с compare-and-swap.

                              > Компилировать в говно с секциями.
                              Ну до такого вроде дело не доходит, емнип обходится циклом с compare-and-swap.

                              > разве не в атомарный add компилируется
                              Нет. Обычный инкремент компилится в самый обычный add и не предполагает никакой атомарности. А вот __sync_add_and_fetch превратится в что-то такое:
                              mov eax, 1
                              xadd var, eax
                              add eax, 1
                              Ответить
                          • почему лол
                            c++03
                            7.1.5.1 The cv-qualifiers /8
                            [Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9 for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. ]

                            ну а слово thread во всем документе упоминается лишь однажды - когда описывается в каком из catch будет выловлено исключение - в ближайшем the try keyword was most recently entered by the thread of control and not yet exited.
                            вот какбэ и вся многопоточность в с++03
                            Ответить
                            • Если не ошибаюсь, новый стандарт вводит модель памяти и многопоточность. Должен уже быть std::thread.
                              Ответить
                              • он говорит про С++03 старый. а то о чем ты говоришь, появилось только в С++11
                                Ответить
                                • >> новый стандарт
                                  > то о чем ты говоришь, появилось только в С++11

                                  Спасибо, ты прям открыл мне глаза
                                  Ответить
                            • Да плевать что там в новом стандарте. Плевать что там пишет мелкософт.
                              volatile исторически был и будет хинтом компилятору:
                              avoid aggressive optimization because the value of the object might be changed

                              Многопоточный код не то место где стоит вводить такого рода "оптимизации" и экономить ставя volatile вместо лочки или cas.
                              Вот если там где написано atomic - это да.

                              Я бы лично не рисковал. Пусть там что в новом стандарте. Всегда найдется глючный компилятор или Тарас, который решит скомпилить под старую студию.
                              Пользователи не будут благодарны за пару сэкономленных тактов. А вот за странные баги они будут ненавидеть и проклинать быдлокодера-оптимизатора.
                              Ответить
                              • это точно мне ответ?
                                Ответить
                                • Не совсем. Сначала это было ответом на твой пост, дескать "вот правильно - в определении volatile нет никаких гарантий о атомарности".
                                  Но потом переросло в ответ про "новый стандарт". И про то что надо соблюдать обратную совместимость, а не слушать мелкософт.
                                  Ответить
                              • > глючный компилятор или Тарас
                                > глючный Тарас
                                No way
                                Ответить
                                • иди нахуй
                                  Ответить
                                • Yes, sir, no way. Taras don't has got bugs. Taras is not a compiler.
                                  Ответить
                                  • >Taras is not a compiler.
                                    He is a robot? Hardware device Taras haven't bugs, but he have bugs in firmware?
                                    Ответить
                                    • тебя еще не заебало?
                                      нас заебало
                                      Ответить
                                      • Sorry, I can't stop it.
                                        Also it in rules is not forbidden.
                                        Ответить
                                        • прими лекарство - съешь винегрет, щи и пельменей, выпей водки
                                          Ответить
                                          • Если нет пельменей, то сходи в магазин.
                                            Попробуй
                                              {
                                                Сварить пельмени.
                                              }
                                            в исключительной ситуации (пельменей в магазине не было) {
                                              Приготовь тесто;
                                              приготовь фарш;
                                              слепи пельмени;
                                              вернись в блок «попробуй».
                                            }
                                            Ответить
                                            • > вернись в блок «попробуй».

                                              без goto пожалуйста.
                                              Ответить
                                        • > Also it in rules is not forbidden.
                                          Почему ты пишешь по-русски английскими словами?
                                          Ответить
                          • >>старый же никаких многопоточностей не предполагал
                            > Лолшто? А зачем тогда вообще volatile ввели?
                            Например для работы в условиях сигналов или прерываний
                            Ответить
                            • >для работы в условиях сигналов или прерываний
                              Если смотреть вглубь, то треды в ОС и реализованы как сигналы и прерывания.
                              Ответить
                          • volatile на тех же контроллерах применяют для memory mapped портов. Захочу я подергать ногой контроллера, напишу
                            Port = 1;
                            Port = 0;

                            А он возьмет соптимизирует это, и выкинет нахер Port = 1, если переменная не volatile. Так что оно и без тредов полезно.
                            Ответить
                        • > А сам по себе ++id для волатильного потокообщего id разве не в атомарный add компилируется?

                          Для волатильного - нет. Он компилируется как и положено в [загрузить - увеличить - записать]. Поэтому баги с инкрементом тут гарантированы.
                          Ответить
                        • Если мне не изменяет память, compare-and-swap (lock cmpxchg на x86) и fetch-and-add (lock xadd) это дорогие операции, работающие на порядки медленнее обычных. Поэтому компилятор не будет пихать их куда попало, если программист явно не укажет, что они нужны.

                          Да и в большинстве случаев атомарность не нужна, например если некая структура защищена мутексом, и мы не забываем его получать и отпускать, то в ней атомарный i++ будет совершенно бесполезным оверхедом.
                          Ответить

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