Re[2]: static && multithreading
От: LuciferMoscow Россия  
Дата: 25.10.05 16:36
Оценка:
Здравствуйте, Alex Alexandrov, Вы писали:

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


LM>>Безопасен ли такой код в многопоточной среде:



AA>[...]


AA>GCC 4.0 по умолчанию синхронизирует создание статических-на-уровне-функции объектов:


AA>

AA>The compiler now uses the library interface specified by the C++ ABI for thread-safe initialization of function-scope static variables. Most users should leave this alone, but embedded programmers may want to disable this by specifying -fno-threadsafe-statics for a small savings in code size.

Неа, MS VC, различные версии
... << RSDN@Home 1.1.4 beta 4 rev. 358>>
Re[10]: static && multithreading
От: remark Россия http://www.1024cores.net/
Дата: 25.10.05 17:07
Оценка:
Здравствуйте, IID, Вы писали:

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


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


LM>>>как раз для использования double lock и просил не предлагать

IID>

IID>double-checking lock суть зло


IID>The pattern is usually unsafe on modern computer hardware and/or optimizing compilers.


IID>One of the dangers of using double-checked locking is that it will often appear to work: it is not easy to distinguish between a correct implementation of the technique and one that has subtle problems. Depending on the hardware platform, the compiler, the interleaving of threads by the scheduler and the nature of other concurrent system activity, failures resulting from an incorrect implementation of double-checking locking may only occur intermittently. Reproducing the failures can be difficult.


IID>стандарт частично решает проблему использованием

IID>
IID>volatile sig_atomic_t integer
IID>


IID> Type of object that can be modified as atomic entity, even in presence of asynchronous interrupts;


IID>однако только теоретически. На практике я бы поостерёгся решения, целиком построенного на соответствии оптимизатора компилятора и стандарта.


Там написано:

Due to the semantics of most programming languages, the code generated by the compiler is allowed to update the shared variable to point to a partially constructed object before A has finished performing the initialization.


Т.е. проблема в том, что разделяемый указатель может указывать на ещё не сконструированный объект.
в коде приведённом выше:

Singleton& Singleton::Instance()
{
   if(pInst_)
      return *pInst_;

   SomeLocker Lock(....);
   if(pInst_)//Вот она, вторая проверка :-/
      return *pInst_;

   static Singleton obj;
   pInst_ = &obj;
   return obj;
}


такой проблемы нет. Объект вначале конструируется полностью, а потом инициализируется разделяемый указатель.
Если инстанс создаётся с помощью new, то для проверки созданности инстанса можно использовать дополнительный флаг, который устанавливается только после полного конструирования объекта.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[11]: static && multithreading
От: elcste  
Дата: 26.10.05 08:09
Оценка: 1 (1) +1
Здравствуйте, remark, Вы писали:

R>Due to the semantics of most programming languages, the code generated by the compiler is allowed to update the shared variable to point to a artially constructed object before A has finished performing the initialization.


R>Т.е. проблема в том, что разделяемый указатель может указывать на ещё не сконструированный объект.

R>в коде приведённом выше:

R>Singleton& Singleton::Instance()
R>{
R>   if(pInst_)
R>      return *pInst_;

R>   SomeLocker Lock(....);
R>   if(pInst_)//Вот она, вторая проверка :-/
R>      return *pInst_;

R>   static Singleton obj;
R>   pInst_ = &obj;
R>   return obj;
R>}

R>такой проблемы нет. Объект вначале конструируется полностью, а потом инициализируется разделяемый указатель.

Так вот. В C++ не гарантируется, что объект obj будет полностью сконструирован к тому моменту, когда проинициализируется указатель pInst_.
Re[12]: static && multithreading
От: remark Россия http://www.1024cores.net/
Дата: 26.10.05 09:29
Оценка:
Здравствуйте, elcste, Вы писали:

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


E>

R>Due to the semantics of most programming languages, the code generated by the compiler is allowed to update the shared variable to point to a artially constructed object before A has finished performing the initialization.


R>>Т.е. проблема в том, что разделяемый указатель может указывать на ещё не сконструированный объект.

R>>в коде приведённом выше:

E>
R>>Singleton& Singleton::Instance()
R>>{
R>>   if(pInst_)
R>>      return *pInst_;

R>>   SomeLocker Lock(....);
R>>   if(pInst_)//Вот она, вторая проверка :-/
R>>      return *pInst_;

R>>   static Singleton obj;
R>>   pInst_ = &obj;
R>>   return obj;
R>>}
E>

R>>такой проблемы нет. Объект вначале конструируется полностью, а потом инициализируется разделяемый указатель.

E>Так вот. В C++ не гарантируется, что объект obj будет полностью сконструирован к тому моменту, когда проинициализируется указатель pInst_.



А к какому же моменту это гарантируется?

В таком коде:

p = 0;
p = new SomeClass();


Я могу понять, что p может стать не 0, ещё до того как отработает конструктор полностью.

Но в таком коде:

temp = new SomeClass();
p = temp;


Это странно.

или в таком:

p = new SomeClass();
bInitialized = true;


Что bInitialized станет равна true, до того как объект инициализируется.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[13]: static && multithreading
От: Bell Россия  
Дата: 26.10.05 09:35
Оценка:
Здравствуйте, remark, Вы писали:

R>А к какому же моменту это гарантируется?


R>В таком коде:

R>
R>p = 0;
R>p = new SomeClass();
R>

R>Я могу понять, что p может стать не 0, ещё до того как отработает конструктор полностью.
R>Но в таком коде:
R>
R>temp = new SomeClass();
R>p = temp;
R>

R>Это странно.
R>или в таком:
R>
R>p = new SomeClass();
R>bInitialized = true;
R>

R>Что bInitialized станет равна true, до того как объект инициализируется.
R>

Почитай ссылку здесь
Автор: alnsn
Дата: 21.10.05

Мне, например, понравилась
Любите книгу — источник знаний (с) М.Горький
Re[13]: static && multithreading
От: elcste  
Дата: 26.10.05 09:49
Оценка:
Здравствуйте, remark, Вы писали:

E>>Так вот. В C++ не гарантируется, что объект obj будет полностью сконструирован к тому моменту, когда проинициализируется указатель pInst_.


R>А к какому же моменту это гарантируется?


А это вообще не гарантируется. Гарантируется только, что наблюдаемое поведение программы будет таким же, как если бы эти действия происходили последовательно.
Re[14]: static && multithreading
От: remark Россия http://www.1024cores.net/
Дата: 26.10.05 10:54
Оценка:
Здравствуйте, Bell, Вы писали:

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


R>>А к какому же моменту это гарантируется?


B>Почитай ссылку здесь
Автор: alnsn
Дата: 21.10.05

B>Мне, например, понравилась

Согласен, предыдущие примеры подкачали. Но!

Эти две головы пишут, что (внимание) Нет способа заставить компилятор выполнять операции выполнять в определённом порядке — чушь:

a = 1;    (1)
...
a = 5;        (2)
c = a + 1;    (3)


В данной ситуации (если не обращать внимание, что это всё можно посчитать в компайл тайме) Операция (3) должна выполниться после (2), т.к. у них зависимость по данным. Это очевидно. Почему бы не воспользоваться этим:


class Singleton
{
public:
    Singleton* get()
    {
        if (m_iFlag)
        {
            Locker lock;
            if (m_iFlag)
            {
                m_instance = new Singleton();
                m_iFlag = m_instance->m_iFake;
            }
        }

        return m_instance;
    }

private:
    std::string m_SomeString;
    int m_SomeMember;
    int m_iFake;

    static int m_iFlag;
    static Singleton* m_instance;

    Singleton()
    : m_SomeString("abc")
    , m_SomeMember(15)
    , m_iFake(rand() + 1)
    {}
};

int Singleton::m_iFlag = 0;
Singleton* Singleton::m_instance = 0;


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

Как Вам такой вариант?


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[15]: static && multithreading
От: elcste  
Дата: 26.10.05 11:39
Оценка:
Здравствуйте, remark, Вы писали:

R>                m_instance = new Singleton();
R>                m_iFlag = m_instance->m_iFake;

R>Проверяем инициилированность по переменной m_iFlag. Установка m_iFlag не может выполниться раньше инициализации объекта,

Почему Вы так решили?

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


Все не в кассу. Помедитируйте над тем, что такое observable behavior и “as-if” rule.
Re[16]: static && multithreading
От: remark Россия http://www.1024cores.net/
Дата: 26.10.05 14:21
Оценка:
Здравствуйте, elcste, Вы писали:

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


E>
R>>                m_instance = new Singleton();
R>>                m_iFlag = m_instance->m_iFake;
E>

R>>Проверяем инициилированность по переменной m_iFlag. Установка m_iFlag не может выполниться раньше инициализации объекта,

E>Почему Вы так решили?


По крайней мере установка m_iFlag не может выполниться раньше инициализации m_iFake. Это-то железно?


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


E>Все не в кассу. Помедитируйте над тем, что такое observable behavior и “as-if” rule.


Вы имеете в виду, что компилятор вполне может решить инициализировать члены не в порядке объявления, а в удобном. Ну в принципе — да. Согласен.

Я упорный

Всё же мы можем управлять выполнения, т.к. не могут переупорядочиваться операции связанные по данным.
Таким образом переписываем конструктор синглтона таким образом:

    Singleton()
    : m_SomeString("abc")
    , m_SomeMember(15)
    , m_iFake(some_hash_func(m_SomeString) + some_hash_func(m_SomeMember))
    {}



Где some_hash_func() — функция "использующая" все члены и выдающая некий хеш.
Т.е. смысл такой, что мы делаем m_iFlag реально зависящий по данным от всех членов синглтона и он не может установиться пока все члены не будут инициализированы! Уаля!



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[17]: static && multithreading
От: Centaur Россия  
Дата: 26.10.05 16:36
Оценка:
Здравствуйте, remark, Вы писали:

R>Я упорный


R>Всё же мы можем управлять выполнения, т.к. не могут переупорядочиваться операции связанные по данным.

R>Таким образом переписываем конструктор синглтона таким образом:

R>
R>    Singleton()
R>    : m_SomeString("abc")
R>    , m_SomeMember(15)
R>    , m_iFake(some_hash_func(m_SomeString) + some_hash_func(m_SomeMember))
R>    {}
R>


R>Где some_hash_func() — функция "использующая" все члены и выдающая некий хеш.


Поскольку члены инициализируются константами, я как разработчик гипотетического злого компилятора имею право посчитать some_hash_func на этапе компиляции и инициализировав m_iFake (и m_iFlag) соответствующими значениями в самом начале инициализации. При этом никто мне не помешает все наблюдаемые побочные эффекты (каковыми являются обращения к функциям стандартной библиотеки и к переменным с квалификатором volatile) оставить на месте.
Re[18]: static && multithreading
От: remark Россия http://www.1024cores.net/
Дата: 27.10.05 07:37
Оценка:
Здравствуйте, Centaur, Вы писали:

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


R>>Я упорный


R>>Всё же мы можем управлять выполнения, т.к. не могут переупорядочиваться операции связанные по данным.

R>>Таким образом переписываем конструктор синглтона таким образом:

R>>
R>>    Singleton()
R>>    : m_SomeString("abc")
R>>    , m_SomeMember(15)
R>>    , m_iFake(some_hash_func(m_SomeString) + some_hash_func(m_SomeMember))
R>>    {}
R>>


R>>Где some_hash_func() — функция "использующая" все члены и выдающая некий хеш.


C>Поскольку члены инициализируются константами, я как разработчик гипотетического злого компилятора имею право посчитать some_hash_func на этапе компиляции и инициализировав m_iFake (и m_iFlag) соответствующими значениями в самом начале инициализации. При этом никто мне не помешает все наблюдаемые побочные эффекты (каковыми являются обращения к функциям стандартной библиотеки и к переменным с квалификатором volatile) оставить на месте.



Ну это же формальности. Способ управлять порядком вычисления то всё равно имеется.
Реализуем some_hash_func() таким образом:

template<typename Type>
int some_hash_func(const Type& obj)
{
    int iResult = 0;

    const char* pBegin = reinterpret_cast<const char*>(&obj);
    const char* pEnd = pBegin + sizeof(obj);

    srand(time());

    for (; pBegin != pEnd; ++pBegin)
    {
        iResult += *pBegin + rand();
    }

    return iResult;
}


Посчитай мне на этапе компиляции.

Сразу оговорюсь, что бы потом вопросов не было, — это реализация для объектов, в которых не содержится указателей. Если в объекте есть указатель, например, std::string, то надо "использовать" таким же образом и память по этому указателю, а то компилятор может только инициализировать этот указатель, а сам объект по указателю ещё не создавать.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[17]: static && multithreading
От: elcste  
Дата: 27.10.05 09:01
Оценка:
Здравствуйте, remark, Вы писали:

R>>>                m_instance = new Singleton();
R>>>                m_iFlag = m_instance->m_iFake;

R>По крайней мере установка m_iFlag не может выполниться раньше инициализации m_iFake. Это-то железно?

При выполнении программы абстрактной машиной — да. Но реализация не должна в точности следовать работе абстрактной машины. Она должна лишь повторять наблюдаемое поведение.

R>Вы имеете в виду, что компилятор вполне может решить инициализировать члены не в порядке объявления, а в удобном.


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

R>Всё же мы можем управлять выполнения, т.к. не могут переупорядочиваться операции связанные по данным.


Это только Ваша фантазия, к сожалению.

R>Таким образом переписываем конструктор синглтона таким образом:


R>    Singleton()
R>    : m_SomeString("abc")
R>    , m_SomeMember(15)
R>    , m_iFake(some_hash_func(m_SomeString) + some_hash_func(m_SomeMember))
R>    {}

Никакой разницы с тем, что было раньше. Если Вы хотите понять, почему эти действия не должны происходить в таком порядке, как Вы полагаете, попытайтесь все же выяснить, что такое наблюдаемое поведение (observable behavior) и почему его нет в Вашем примере.

Это не единственная проблема с реализацией DCLP, но и этого достаточно.
Re[19]: static && multithreading
От: elcste  
Дата: 27.10.05 09:07
Оценка:
Здравствуйте, remark, Вы писали:

R>template<typename Type>
R>int some_hash_func(const Type& obj)
R>{
R>    int iResult = 0;

R>    const char* pBegin = reinterpret_cast<const char*>(&obj);
R>    const char* pEnd = pBegin + sizeof(obj);

R>    srand(time());

R>    for (; pBegin != pEnd; ++pBegin)
R>    {
R>        iResult += *pBegin + rand();
R>    }

R>    return iResult;
R>}

R>Посчитай мне на этапе компиляции.

А это, кстати, вообще undefined behavior (если, конечно, у параметра шаблона не будет перегружен оператор взятия адреса — тогда может быть и просто ill-formed). Это компилятор не то что считать, а даже транслировать не обязан.
Re[20]: static && multithreading
От: remark Россия http://www.1024cores.net/
Дата: 27.10.05 09:58
Оценка:
Здравствуйте, elcste, Вы писали:

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


E>
R>>template<typename Type>
R>>int some_hash_func(const Type& obj)
R>>{
R>>    int iResult = 0;

R>>    const char* pBegin = reinterpret_cast<const char*>(&obj);
R>>    const char* pEnd = pBegin + sizeof(obj);

R>>    srand(time());

R>>    for (; pBegin != pEnd; ++pBegin)
R>>    {
R>>        iResult += *pBegin + rand();
R>>    }

R>>    return iResult;
R>>}
E>

R>>Посчитай мне на этапе компиляции.


E>А это, кстати, вообще undefined behavior (если, конечно, у параметра шаблона не будет перегружен оператор взятия адреса — тогда может быть и просто ill-formed). Это компилятор не то что считать, а даже транслировать не обязан.



Хорош ко всем мелочам придираться. some_hash_func() можно реализовать и без UB. Или ты и с этим не согласен? Я имею в виду идею: если флаг инициализации зависит от всего что надо инициализировать, что бы считать, что экземпляр создан, при этом исключаем компайл-тайм вычисления введением некой рунтайм составляющей.

... хотя я уже сам начинаю, что в конечном итоге это может быть не возможно

Ну или вот другая идея: в статье Саттер пишет, что у нас нет возможности спрятать от агрессивного компилятора/линковщика тело какой-то функции. Ну это если функция находится статически линкуется со всей программой. А если она в dll'ке?


...
  p = new Singleton();
  bFlag = FunctionNotAvailableToLinker(p);
...


FunctionNotAvailableToLinker(Singleton* p)
{
  return true;
}



Сейчас я знаю, что Вы скажете, что код соптимизируется в момент загрузки программы в ОП
Ну а если это динамически-линкуемая-отложенно-загружаемая-библиотека. Или COM, установленный на удаленной машине.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[21]: static && multithreading
От: elcste  
Дата: 27.10.05 12:07
Оценка:
Здравствуйте, remark, Вы писали:

E>>А это, кстати, вообще undefined behavior (если, конечно, у параметра шаблона не будет перегружен оператор взятия адреса — тогда может быть и просто ill-formed). Это компилятор не то что считать, а даже транслировать не обязан.


R>Хорош ко всем мелочам придираться.


Это, спору нет, мелочи. Просто если уж общее утверждение пытаться опровергнуть конкретным примером, необходимо, чтобы пример был корректным, n'est-ce pas?

R>some_hash_func() можно реализовать и без UB. Или ты и с этим не согласен? Я имею в виду идею: если флаг инициализации зависит от всего что надо инициализировать, что бы считать, что экземпляр создан, при этом исключаем компайл-тайм вычисления введением некой рунтайм составляющей.


С этим-то я согласен. Просто идея обсуждаемой проблеме ортогональна.

R>Ну или вот другая идея: в статье Саттер пишет, что у нас нет возможности спрятать от агрессивного компилятора/линковщика тело какой-то функции.


Теоретически нет. А практически есть, конечно. Поэтому, в частности, не нужен volatile при использовании примитивов синхронизации. Но это уже отдельная интересная тема.

R>Ну это если функция находится статически линкуется со всей программой. А если она в dll'ке?


А где в стандарте DLL? Т.е. стандарт их, конечно, позволяет. Но ведь наличия их не требует.


Я вот чего сказать хотел.

(1) В общем случае невозможно гарантировать корректную работу C/C++ программы в многопоточной среде без введения дополнительных ограничений на реализацию (какие накладывает, например, POSIX).

Конкретная многопоточная программа может работать корректно в той или иной реализации, но для того, чтобы знать в каких условиях она будет работать, а в каких нет, нужно понимать, почему (1).
Re[22]: static && multithreading
От: remark Россия http://www.1024cores.net/
Дата: 27.10.05 12:33
Оценка:
Здравствуйте, elcste, Вы писали:

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


E>>>А это, кстати, вообще undefined behavior (если, конечно, у параметра шаблона не будет перегружен оператор взятия адреса — тогда может быть и просто ill-formed). Это компилятор не то что считать, а даже транслировать не обязан.


R>>Хорош ко всем мелочам придираться.


E>Это, спору нет, мелочи. Просто если уж общее утверждение пытаться опровергнуть конкретным примером, необходимо, чтобы пример был корректным, n'est-ce pas?


R>>some_hash_func() можно реализовать и без UB. Или ты и с этим не согласен? Я имею в виду идею: если флаг инициализации зависит от всего что надо инициализировать, что бы считать, что экземпляр создан, при этом исключаем компайл-тайм вычисления введением некой рунтайм составляющей.


E>С этим-то я согласен. Просто идея обсуждаемой проблеме ортогональна.


R>>Ну или вот другая идея: в статье Саттер пишет, что у нас нет возможности спрятать от агрессивного компилятора/линковщика тело какой-то функции.


E>Теоретически нет. А практически есть, конечно. Поэтому, в частности, не нужен volatile при использовании примитивов синхронизации. Но это уже отдельная интересная тема.


R>>Ну это если функция находится статически линкуется со всей программой. А если она в dll'ке?


E>А где в стандарте DLL? Т.е. стандарт их, конечно, позволяет. Но ведь наличия их не требует.



E>Я вот чего сказать хотел.


E>(1) В общем случае невозможно гарантировать корректную работу C/C++ программы в многопоточной среде без введения дополнительных ограничений на реализацию (какие накладывает, например, POSIX).


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



Конкретная многопоточная программа (которая писалась специально для того, что она не будет работать) может и не работать в многопоточной среде.
Для меня главное, что я могу гарантировать, что моя программа на любой реальной платформе всегда будет работать. И так никогда не случиться, что моя программа молча перестанет когда-либо работать.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[23]: static && multithreading
От: elcste  
Дата: 27.10.05 12:55
Оценка:
Здравствуйте, remark, Вы писали:

R>Для меня главное, что я могу гарантировать, что моя программа на любой реальной платформе всегда будет работать.


Ну, а это значит, что Вы не сможете использовать в своих программах DCLP. Потому как он будет работать не везде.
Re[24]: static && multithreading
От: remark Россия http://www.1024cores.net/
Дата: 27.10.05 13:17
Оценка:
Здравствуйте, elcste, Вы писали:

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


R>>Для меня главное, что я могу гарантировать, что моя программа на любой реальной платформе всегда будет работать.


E>Ну, а это значит, что Вы не сможете использовать в своих программах DCLP. Потому как он будет работать не везде.


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

1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.