1. C# / Говнокод #11989

    +141

    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. 8
    public sealed class CustomProvider
    {
         private readonly object _SyncRoot = new object();
     
        public CustomProvider()
        {
                 lock(_SyncRoot)
                 {

    А вообще, если честно, создание экземпляра класса предка (object) всех классов, чтобы произвести захват критической секции - это так печально. Одному мне эта техника кажется удобной, но странной? Нет чтобы создать класс CriticalSection или что-то такое. А они создают объект совсем не связанный с синхронизацией. Не самодокументированно и тут явно какая-то переголова скрывается при создании объекта, по затратам памяти и системных ресурсов, что в каждый объект на уровне имплементации языка приходится по критической секции добавлять.
    Кстати, там как реализована эта критическая секция? Хендл этой критической секции фактически системный ресурс и для него по идеи нужно вызывать Dispose, но это не происходит. Тогда почему этих системных хендлов критической секции хватает, хотя ситуация без Dispose похожа на утечку системных ресурсов?

    Если кто подумал про lock(this), то это плохо с точки зрения проектирования класса и его последующего использования, поэтому идея с private _SyncRoot - это правильно. Ведь lock(this) (под this в последнем случае имеется данный экземпляр класса), может использоваться и снаружи класса, что может быть пересинхронизацией.

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

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

    • http://msdn.microsoft.com/en-us/library/aa664735(v=vs.71).aspx
      A lock statement of the form
      lock (x) ...
      where x is an expression of a reference-type, is precisely equivalent to
      System.Threading.Monitor.Enter(x);
      try {
      ...
      }
      finally {
      System.Threading.Monitor.Exit(x);
      }

      "критическая секция" если и реализована, то только в System.Threading.Monitor
      Ответить
      • >"критическая секция" если и реализована, то только в System.Threading.Monitor
        Ты хочешь сказать, что на самом деле критическая секция глобальна на весь домен/приложение? Не верю. Думаю они в каждый объект напихали по крит секции.
        Ответить
        • http://ru.wikipedia.org/wiki/Монитор_(синхронизация)
          В .Net реализована своя синхронизация в потоках, с блокировками и ожиданиями но без использования Win32 primitives
          Ответить
        • Ну вот в Qt5 мьютексы перепилили на подобную архитектуру. Один тяжеловесный объект на каждый тред + очень легковесные мьютексы. Их можно плодить буквально тысячами.

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

          P.S. Ну и да, там тоже вроде бы используют свои примитивы вместо системных, ибо так шустрее.
          Ответить
          • да, ассемблерные реализации test-and-set для каждой поддерживаемой платформы
            Ответить
    • > если честно, создание экземпляра класса предка (object) всех классов, чтобы произвести захват критической секции - это так печально.
      ничего не понял. чего печально?
      создает наиболее легковесный ссылочный тип. в управляемой куче в sync block'е для него указывается ID текущего треда. и данный тред входит в критическую секцию, а обхект становится флагом выхода.

      печально вешать лок в конструкторе, это действительно неправильно и говорит о плохом дизайне.
      Ответить
      • В Java видел использование в качестве лока Object[] = new Object[0]; - в комментах автор писал, что по памяти оказывается легче простого Object.

        А лок в конструкторе - да, не комильфо.
        Ответить
        • > в комментах автор писал, что по памяти оказывается легче простого Object
          странно, ведь массив тоже является объектом
          Ответить
        • чем плох лок в конструкторе?
          Ответить
          • Объясняю. Как мы видели недавно на примере using из конструкторов и инициализаторов нехорошо кидать исключения. И даже с точки зрения GC, которому потом собирать недостроенный объект. Если конструктор кидает исключения 95% что кривое проектирование. То же с локами - не вижу никакого смысла. К тому же если там стоит какой-то wait.
            Конструкторы нужны только чтобы выделять память и инициализировать переменные.

            А локи и исключения оставьте для методов, в том числе фабричных.
            Ответить
            • > Конструкторы нужны только чтобы выделять память и инициализировать переменные.
              Если только язык не поддерживает RAII
              Ответить
            • Смысла нет, допустим. Но чем плох-то? Что такого критического может случиться на практике: утечка ресурсов/взаимоблокировки/атомная война? Что?
              Ответить
              • Но зачем? Лок на весь конструктор - это означает что два потока не смогут создавать одновременно. Зачастую лочку хватают перед конструктором и освобождают позже. Так гибче. А если сильно хочется однопоточности, то еще сделай конструктор приватным.

                >Что такого критического может случиться на практике
                Всё зависит от того зачем хватать лочку. Если просто лок, то ничего.
                А если для waitа - чревато исключением и неосвобожденными ресурсами.
                http://govnokod.ru/11950#comment156472

                А пытаться осбождать ресурсы в конструкторе, при неудачном создании - это верх идиотии.
                Ответить
                • >Но зачем? Лок на весь конструктор - это означает что два потока не смогут создавать одновременно.
                  Что? Тут в данном говнокоде лолк только на поле текущего объекта (не статическое поле). То есть в данном случае конструктор может вызываться параллельно в нескольких потоках для разных объектов. Может я чего не понимаю...
                  Ответить
                  • > в данном говнокоде лолк только на поле текущего объекта
                    Это вообще бессмысленное говно. Я говорю о сколь-нибудь разумном использовании.
                    Ответить
                  • Вообще говоря, да - у каждого экземпляра объекта должен быть свой лок. Вероятность того, что методы объекта будут вызывать до окончания его конструирования, очень мала...
                    Ответить
                    • >Вероятность того, что методы объекта будут вызывать до окончания его конструирования, очень мала...
                      По моему она 0вая
                      Ответить
                      • В жабе, вообще говоря, модель памяти позволяет одному потоку увидеть недоконструированный объект, созданный в другом потоке. Если только у объекта в конструкторе не происходит присвоения final-полям.
                        Ответить
                        • >Если только у объекта в конструкторе не происходит присвоения final-полям.
                          HotSpot следует консервативной рекомендации из JSR-133 Cookbook: «Issue a StoreStore barrier after all stores but before return from any constructor for any class with a final field.»

                          Другими словами, есть специфичная для хотспота фишка:

                          // This method (which must be a constructor by the rules of Java)
                          // wrote a final. The effects of all initializations must be
                          // committed to memory before any code after the constructor
                          // publishes the reference to the newly constructor object.
                          // Rather than wait for the publication, we simply block the
                          // writes here. Rather than put a barrier on only those writes
                          // which are required to complete, we force all writes to complete.

                          То есть, если hotspot обнаруживает в конструкторе запись хотя бы в одно final поле, то он тупо выставляет барьер в конец конструктора и таким образом обеспечивает запись всех полей в конструкторе до записи ссылки на сконструированный объект. Это имеет смысл, чтобы не делать несколько барьеров для нескольких финальных полей.
                          Ответить
                      • http://ideone.com/6xbzJT
                        Без строчки кода
                        Main.callee = this;
                        порядок строк в выводе обратный
                        Ответить
                        • Странно, но жаба делает безопасную публикацию даже там, где она, в общем-то, не гарантирована (ни статиков, ни volatile, ни final, ни синхронизаций)
                          http://ideone.com/YbB4K2
                          Ответить
                          • Полный разбор полётов с тестами и ассемблером.
                            http://habrahabr.ru/post/143390
                            Сорри за хабр.
                            /thread
                            Ответить
                            • я уже читал эту статью... Но понять, почему объект безопасно опубликовался (?) там, где, в теории, не должен был, не могу.
                              Ответить
                              • Всё, как ты понимаешь, зависит от того в каком порядке компилятор поставил код и как сработали треды.
                                Это вероятность. Русская рулетка с прицелом в ногу.
                                Ответить
                                • Я бы тоже так объяснил, если бы объект конструировался не целую секунду... а, ладно, хрен с ним.
                                  Ответить
                    • > Вероятность того, что методы объекта будут вызывать до окончания его конструирования, очень мала...

                      Однажды ухитрились так сделать. В конструктор объекта передали ссылку на ее слушатель, аффтар из конструктора дернул у слушателя метод, а слушатель в этом методе дернул объект.

                      На большинстве Ява-машин прошло, но на некоторых умирало с NullPointerException.
                      Ответить
                  • > То есть в данном случае конструктор может вызываться параллельно в нескольких потоках для разных объектов.
                    Ну да, поэтому я не понимаю нахрена он тут нужен. Разве что защищать недоконструированный объект от вызова других методов....
                    Ответить
                    • Безопастная публикация, синглтоны.
                      Весь прикол в том, что объект должен как-то попасть в статик-поле, что несколько тредов могли его прочесть, следовательно должен быть фабричный метод. Без него никак.
                      Ответить
                      • Точняк!
                        public CustomProvider()
                            {
                                     lock(_SyncRoot)
                                     {
                        //...
                        OtherClass::staticMember1=this;
                        Ответить
                        • Дальше можно обращаться в OtherClass::staticMember1 из другого потока
                          Ответить
                          • Мсье знает толк в извращениях... А если я создам второй инстанс этого объекта? ;)
                            Ответить
                • Что плохого в кидании исключений из конструкторов? Как-бы, если нет функции-крейтора/фабрики, то вброшенное исключение - единственный способ сообщить о несконструированном объекте.
                  Ответить
    • Слизали с жабы, а там вообще говна навалом
      - неявный монитор есть у каждого объекта, в 99% случаев он не используется
      - Clonable, обсуждавшийся недавно
      - serialization - это вообще треш-угар-содомия: интерфейс Serialazible не объявляет методов, для кастомизации сериализации нужно объявлять магические приватные методы
      - можно изменить System.out, присвоить ему null
      etc.
      Ответить
      • >Clonable, обсуждавшийся недавно
        Можно ссылку на тред? Разве есть чем заменить Clonable, кроме громоздких клонических фабрик?
        Ответить
      • >serialization - это вообще треш-угар-содомия
        Я так понимаю вы про отсутствие кастомной сериализации? А так сериализация нужна. Имхо кастомная не сильно нужна. Думаю, если сильно понадобилась какая-то гиперкастомная, то сериализовать нужно через рефлекшен.
        Ответить
        • Нет, она просто реализована через анус. У интерфейса Serializable нет методов. Вообще нет. Юзается тупо как аннотация того, что объект сериализуем.

          Вот его описание: http://docs.oracle.com/javase/6/docs/api/java/io/Serializable.html.
          Ответить
        • Сериализация есть, и её можно переопределить. Только ООП там и не пахнет, сплошная магия. Кастомная сильно нужна: сериализовать хэш-таблицу или синглтон нужно особым образом. К тому же, сериализация всегда представлялась для меня внешним сервисом, а не свойством самого объекта.
          Ответить
      • а еще сабж оч напомнил жабосвиный synchronized (treeLock) {...}
        Ответить

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