Re: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 16.01.09 17:19
Оценка: 26 (5)
Здравствуйте, Alexander G, Вы писали:

AG>Есть ли тут гонка ?


AG>
long i; // глобальная переменная

AG>void f() // вызывается из нескольких потоков выполнения одновременно
AG>{
AG>  i = OSVersion(); // возврашает каждый раз одно и то же
AG>  /* дальше используется i. больше нигде она не используется */
AG>}



Зависит от того, что назвать гонкой.
Если взять формальное определение POSIX (более одного потока одновременно обращаются к ячейке памяти, причём хотя бы один поток на запись), то гонка здесь, очевидно, есть. Со всеми вытекающими — UB.

Microsoft явно не документирует модель памяти Windows, хотя, видимо, подоплёка такая же как и в POSIX. Поэтому тоже формально гонка и UB.

C++09 — гонка и UB.

С++03/С99 — формально не документировано.

Java/C# — нет гонок (в том смысле, что считываемое значение детерминировано).

Если смотреть с практической стороны — т.е. гонка есть "нехорошее" поведение, то, в принципе, должно работать на всех распространенных аппаратных платформах и компиляторах. На всех распространенных аппаратных платформах сохранения/загрузки размером со слово по выровненным адресам будут вообще атомарны (на X86/Itanium, на которых работает Windows, даже и 16-ти байтные обращения будут атомарны). А что бы компилятор это попортил,.. ну он должен быть сильно невменяемым (хотя опять же — это не гарантируется).


Но самое интересное начинается дальше. Твой пример — это т.н. "синхронизация на флаге" (немного модифицированная). Она работает. Но если мы возьмём т.н. "синхронизацию на флаге с зависимым состоянием", то что ты приводил в одном из постов, то она уже не будет работать:
void init()
{
  if (initialized)
    return;
  data.p1 = ::GetModuleHandle(...);
  data.p2 = ::GetProcAddress(...);
  data.i3 = (int)sqrt(42*_WIN_VER);
  initialized = true;
}



Если тебя интересует MSVC+Windows, то это аппаратные платформы IA-32, Intel 64 И IA-64. На IA-32/Intel 64 это не будет работать из-за возможных переупорядочиваний компилятором, т.е. компилятор волен сгенерировать, допустим, такой код:
void init()
{
  if (initialized)
    return;
  data.p1 = ::GetModuleHandle(...);
  initialized = true;
  data.p2 = ::GetProcAddress(...);
  data.i3 = (int)sqrt(42*_WIN_VER);
}


На IA-64 это не будет работать из-за возможных переупорядочиваний компилятором + из-за возможных переупорядочиваний аппаратным обеспечением. Т.е. если даже компилятор сгенерирует "правильный" код, т.е. аппаратное обеспечение всё-равно вольно это выполнить так, как показано в примере с переупорядочиванием компилятором.

Для MSVC самый просто способ решить это — это добавить volatile к определению initialized. MSVC фактически расширяет значение volatile с примитива для работы с оборудованием до примитива синхронизации потоков. Т.о. volatile и подавит переупорядочивания компилятором, и заставит компилятор вставить необходимые инструкции (барьеры памяти) для подавления переупорядочивания аппаратным обеспечением (для IA-64). Однако это будет иметь некоторую ран-тайм стоимость на IA-64. Если барьер при сохранении в initialized не интересен, т.к. будет выполнен только один раз за время работы программы, то барьер при считывании может быть неприятен, т.к. будет добавлять порядка пары десятков тактов. Хорошие новости то, что его можно элиминировать следующим образом:
struct data_t
{
  void * p1;
  void * p2;
  int  i3;
  ...
} data = {0};

data_t* p_data = 0;

data_t* init()
{
    data_t* p = p_data;
    _ReadWriteBarrier();
    if (p)
        return p;
    data.p1 = ::GetModuleHandle(...);
    data.p2 = ::GetProcAddress(...);
    data.i3 = (int)sqrt(42*_WIN_VER);
    *((data_t* volatile*)(&p_data)) = &data;
    return p_data;
}


Ключевой момент здесь — это замена флага initialized на указатель p_data. Это — искуственная инджекция зависимости по данным между загрузкой флага и загрузкой самого объекта. Все современные процессоры автоматически соблюдают упорядочивание при зависимости по данным, в данном случае — между загрузкой указателя и загрузкой данных через этот указатель. Реализация сохранения во флаг тут реализуется так же — т.е. через volatile.


Хотя, в принципе, конечно, желательно кодировать не против конкретной платформы, а против портабельного стандарта; и спрашивать не "почему это может не работать?", а "кем гарантировано, что это должно работать?" (я имею в виду использование мьютексов и т.д). Однако курьёз в том, что Windows (я так понимаю речь идёт о нём) не предоставляет средств для корректной инициализации объектов (по крайней мере до Vista). В POSIX threads (pthread) всё проще — он из покон веков предоставляет макрос PTHREAD_MUTEX_INITIALIZER для *статической* инициализации глобальных мьютексов. boost.thread с (по-моему) 1.35 предоставляет аналогичную функциональность и под Windows (кстати, это была одна из основных причин для переписывания мьютекса с CRITICAL_SECTION на собственный велосипед на Interlocked операциях). А тащить в проект thread-win32 или boost только из-за этого видится не меньшим злом...

Есть ещё вариант с т.н. Lakos Singleton (nifty counting trick). Он инициализируется при инициализации глобальных объектов, однако старается это делать до любого его использования:
//data.h
struct data_t
{
    void * p1;
    void * p2;
    int  i3;
    //...
};

template<typename>
struct data_initializer
{
    static unsigned counter;
    static data_t data;

    data_initializer()
    {
        if (++counter == 1)
        {
            data.p1 = ::GetModuleHandle(0);
            data.p2 = ::GetProcAddress(0, 0);
            data.i3 = (int)(42*_WIN32_WINNT);
        }
    }

    ~data_initializer()
    {
        if (--counter == 0)
        {
            // deinitialize data - nobody uses it now
        }
    }
};

template<typename T>
unsigned data_initializer<T>::counter = 0;

template<typename T>
data_t data_initializer<T>::data = {};

static data_initializer<void> data_initializer_; // internal linkage


В принципе, его можно обмануть при желании, поэтому желательно этот заголовочный файл включить во все файлы проекта, stdafx.h подойдёт. Тогда объект будет создан во время инициализации глобальных объектов, однако гарантированно до любого его использования. Плюс не вносит никаких издержек во время выполнения, т.е. никаких дополнительных проверок и индирекций при доступе к объекту.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[15]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 02.02.09 18:14
Оценка: 11 (2) +1
Здравствуйте, Alexander G, Вы писали:

AG>Я не зочу volatile т.к. его смысл зависит от компилятора.

AG>Может тогда так ?

_WriteBarrier() ещё больше зависит от компилятора (intrinsic MSVC). А если код только под MSVC, то проще и правильнее использовать volatile.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[14]: Race condition
От: bkat  
Дата: 05.01.09 16:29
Оценка: -2 :)
Здравствуйте, Alexander G, Вы писали:


AG>Этот код не рабочий, т.к. getInstance может быть вызван до конструктора QMutex.


Смотри внимателей.
mutex — статический, а иначе ты бы его не смог пользовать в статическом методе getInstance.

Я понимаю, что ты просто не знаешь, как пользоваться CRITICAL_SECTION.
Почитай тут:
http://www.rsdn.ru/article/baseserv/critsec.xml
Автор(ы): Павел Блудов
Дата: 14.03.2005
В статье рассматриваются аспекты работы с критическими секциями, их внутреннее устройство и способы отладки


Заодно там же и найдешь демо, как можно сделать обертки виндовых критических секций,
чтобы ими можно было бы пользоваться по типу Qt:
http://www.rsdn.ru/article/baseserv/critsec/cswrap.zip

Да и вообще тут много чего полезного:
http://www.rsdn.ru/summary/1390.xml

P.S. Конструкторы у объектов есть всегда.
Re[10]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 30.01.09 09:51
Оценка: 4 (1) +1
Здравствуйте, Quasi, Вы писали:

Q>Для процессоров IA-32 и Intel 64 (Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Intel Xeon, P6 family, Pentium 4 и более ранних),

Q>AMD64 и более ранних операции чтения из памяти и запись всегда имеют семантику Acquire и Release соответственно (если не считать "string operations" и "streaming stores"). Однако это не мешает и вышеописанные процессоры (кроме совсем ранних) переупорядочивают чтение и предшествующую запись, если они не относятся к одному адресу памяти.

АФАИК, они переупорядочивают даже обращения к одному адресу памяти. Свои обращения процессор-то, конечно, увидит в программном порядке, а вот другие процессоры могут увидеть обращения к одному адресу памяти в отличном от программного.
Единственное, что сейчас видимо не переупорядочивается (хотя АФАИК это не документировано) — это запись и чтение по одному адресу, но с разными размерами (т.н. collocation trick):

volatile char x = 0;
volatile char y = 0;

// thread 1
x = 1;
short r1 = *(volatile short*)&x;

// thread 2
y = 1;
short r2 = *(volatile short*)&x;



Q>Из-за возможного переупорядочивания в результате выполнения возможна ситуация, когда r1 = r2 = 0

Q>Некоторые алгоритмы могут полагаться на строгую последовательность операций чтения и предшествующей записи, поэтому реализация барьера для процессора должна содержать либо инструкцию mfence, либо инструкцию с префиксом "LOCK" в случае вышеописанных процессоров. Соответсвенно, по факту функция _ReadWriteBarrier не реализует барьер памяти для процессора, а лишь барьер памяти для компилятора:
Q>

Q>A memory barrier prevents the compiler from optimizing memory accesses across the barrier, but enables the compiler to still optimize instructions between barriers.


Q>Единственное, что вводит в ложное заблуждение в документации Microsoft, так это:

Q>

Marking memory with a memory barrier is similar to marking memory with the volatile (C++) keyword.


А почему это вводит в заблуждение? Ведь volatile в данном случае тоже не приведёт к добавлению mfence/lock.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[15]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 16:49
Оценка: 1 (1) -1
Здравствуйте, bkat, Вы писали:

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


AG>>Этот код не рабочий, т.к. getInstance может быть вызван до конструктора QMutex.


B>Смотри внимателей.

B>mutex — статический, а иначе ты бы его не смог пользовать в статическом методе getInstance.

Да. но порядок инициализации статических объектов не определён.
Конструкторы статических объектов ничем не лучше конструкторов глобальных объектов, и так же вызываются в неопределённом порядке.

т.е. в этом коде ничего нельзя сказать про порядок S1, S2, S3:

struct S { S() }

S s1;

struct X
{
  static S s2;
}

S X::s2;

S s3;


B>Я понимаю, что ты просто не знаешь, как пользоваться CRITICAL_SECTION.

B>Почитай тут:
B>http://www.rsdn.ru/article/baseserv/critsec.xml
Автор(ы): Павел Блудов
Дата: 14.03.2005
В статье рассматриваются аспекты работы с критическими секциями, их внутреннее устройство и способы отладки


Я знаю как ими пользоваться. Это ты похоже не понял проблему
Если бы всё было так просто, я бы действительно взял КС.

B>P.S. Конструкторы у объектов есть всегда.


Да, но может не быть пользовательских конструкторов. Тогда возможна статическая инициализаций, как с S1 ниже. Такая инициализация гарантирована до инициализации S2.

struct S1
{
  int i;
} s2 = {1};

struct S2
{
  int i;
  S2() : i(1) {}
} s2 = 1;
Русский военный корабль идёт ко дну!
Re[12]: Race condition
От: bkat  
Дата: 05.01.09 16:12
Оценка: :))
Здравствуйте, Alexander G, Вы писали:

AG>нет, покажи полностью, с самой КС, чтобы было cs.Lock() или EnterCriticalSection(&cs); .

AG>А то непонятно как то что было показано может работать.

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

Вот, как бы это выглядело бы на Qt

struct data_t
{
  static data_t* getInstance()
  {
    QMutexLocker locker(&mutex);
    static data_t* instance = new data_t;
    return instance;
  }
  static QMutex mutex;
};


Считай, QMutex в конструкторе инициализирует CRITICAL_SECTION
QMutexLocker в конструкторе вызывает EnterCriticalSection,
а в деструкторе — LeaveCriticalSection.
Re[9]: Race condition
От: Quasi  
Дата: 30.01.09 09:08
Оценка: 30 (1)
Здравствуйте, Alexander G, Вы писали:

AG>Ну т.е. нет никаких свидетельств что барьеры "хуже" volatile ?


AG>(ассемблера IA64 не знаю, для IA64 не собираю)


R>>


Полностью согласен с remark, однако хотелось бы прояснить некоторые моменты:

Для процессоров IA-32 и Intel 64 (Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Intel Xeon, P6 family, Pentium 4 и более ранних),
AMD64 и более ранних операции чтения из памяти и запись всегда имеют семантику Acquire и Release соответственно (если не считать "string operations" и "streaming stores"). Однако это не мешает и вышеописанные процессоры (кроме совсем ранних) переупорядочивают чтение и предшествующую запись, если они не относятся к одному адресу памяти.

volatile long x = 0;
volatile long y = 0;
....
//Processor A
x = 1;
long r1 = y;
....
//Processor B
y = 1;
long r2 = x;

Из-за возможного переупорядочивания в результате выполнения возможна ситуация, когда r1 = r2 = 0
Некоторые алгоритмы могут полагаться на строгую последовательность операций чтения и предшествующей записи, поэтому реализация барьера для процессора должна содержать либо инструкцию mfence, либо инструкцию с префиксом "LOCK" в случае вышеописанных процессоров. Соответсвенно, по факту функция _ReadWriteBarrier не реализует барьер памяти для процессора, а лишь барьер памяти для компилятора:

A memory barrier prevents the compiler from optimizing memory accesses across the barrier, but enables the compiler to still optimize instructions between barriers.


Единственное, что вводит в ложное заблуждение в документации Microsoft, так это:

Marking memory with a memory barrier is similar to marking memory with the volatile (C++) keyword.


По упорядочиванию процессоров:
Intel® 64 and IA-32 Architectures Software Developer’s Manual (7.2 Memory ordering)
AMD64 Architecture Programmer’s Manual (7.2 Multiprocessor Memory Access Ordering)
Intel® Itanium® Architecture Software Developer’s Manual (Part II, 2.2 Memory Ordering)
Re[13]: Race condition
От: Quasi  
Дата: 02.02.09 16:54
Оценка: 30 (1)
Здравствуйте, Alexander G, Вы писали:

AG>Т.е. надо вставлять _mm_fence вручную ?


AG>
AG>struct data_t
AG>{
AG>  void * p1;
AG>  void * p2;
AG>  int  i3;
AG>  ...
AG>} data = {0};

AG>data_t* p_data = 0;

AG>data_t* init()
AG>{
AG>    if (p_data)
AG>        return p_data;
AG>    _ReadWriteBarrier();
AG>    data.p1 = ::GetModuleHandle(...);
AG>    data.p2 = ::GetProcAddress(...);
AG>    data.i3 = (int)sqrt(42*_WIN_VER);
AG>    _mm_mfence();
AG>    p_data = &data;
AG>    return p_data;
AG>}
AG>


Принципиальным здесь является наличие зависимости по данным, которая гарантирует, что на всех современных процессорах чтения указателя p_data и данных по указателю не будут переупорядочены. Однако, это не налагает ограничений на переупорядочивание записи p_data и данных. В данном случае для предотвращения переупорядочивания достаточно записи указателя p_data c release семантикой (на x86, Intel 64 и AMD64 для этого ничего дополнительно не требуется). Если верить документации Microsoft по квалификатору volatile, то для переносимости на другие аппаратные платформы можно воспользоваться трюком, который описал remark:
//.....
*((data_t* volatile*)(&p_data)) = &data;
return p_data;

На других компиляторах без соответствующих acquire, release семантик для чтения, записи volatile данных можно использовать атомарную операцию, аналогичную ::InterlockedExchange в WINAPI, имеющую либо release, либо fence семантику (full memory barrier, как в случае с ::InterlockedExchange).
Re[14]: Race condition
От: alsemm Россия  
Дата: 16.01.09 19:08
Оценка: 10 (1)
Здравствуйте, remark, Вы писали:

R>Здравствуйте, Кодт, Вы писали:


К>>Сам мьютекс (и все остальные примитивы lock-based синхронизации) нужно инициализировать в рантайме.

К>>Следовательно, мы всё равно хоть что-то, да должны сделать на lock-free.

R>Это только в мире Windows, в мире POSIX (или pthread-win32) мы можем просто написать:

R>
R>pthread_mutex_t g_guard = PTHREAD_MUTEX_INITIALIZER;
R>

R>в мире boost:
R>
R>boost::detail::basic_recursive_mutex g_guard = BOOST_BASIC_RECURSIVE_MUTEX_INITIALIZER;
R>

Это все работать будет, если только все эти красивые макросы разворачиваются в константы.
В MS Windows никаких констант нет, значит инициализация g_guard будет не на этапе загрузки секций кода из exe в память, а позже, когда возможно уже какие-то глобальные функции вызываются.

to Alexander G: вас спасут именованные мьютексы. Если из нескольких потоков запрашивать создать мьютекс с одним и тем же именем, то создан будет он только в одном потоке, а в остальные будет возвращен handle созданного мьютекса.

Как-то так:
class Globals
{
    static Globals* instance_;
public:
    Globals*
    getInstance() // nothrow
    {
        HANDLE guard = CreateMutex(0, false, "unique-name-of-your-taste");
        if (NULL == guard)
        {
            return 0;
        }

        if (WAIT_FAILED == ::WaitForSingleObject(impl, INFINITE))
        {
            return 0;
        }

        if (0 == globals_)
        {
            globals_ = new (std::nothrow) Globals();
        }

        ::ReleaseMutex(guard);

        return globals_;
    }
};


По хорошему надо еще где-то
delete globals_;
::CloseHandle(<тот-самый мьютекс>);

делать, т.к. если память выделенную для globals_ система может и почистит сама, после завершения приложения, то именованный мьютекс — вряд-ли.

Алексей
Re[5]: Race condition
От: Кодт Россия  
Дата: 05.01.09 13:35
Оценка: 6 (1)
Здравствуйте, Alexander G, Вы писали:

К>>Разве что для надёжности можешь заменить неатомарные присваивания атомарными — InterlockedExchange / InterlockedCompareExchange.

AG>InterlockedExchange тоже не хочу, прийдётся писать volatile и терять возможную оптимизацию при дальнейшем использовании.

volatile можешь присобачить по месту, в const_cast. Всё равно твой сценарий не предполагает, что там будет нечто постоянно меняющееся, так что пусть компилятор сколько угодно оптимизирует чтение после инициализации.

AG>А что будет значить "для надёжности" ?


На случай той самой экзотической платформы (или взбесившегося компилятора), который в процессе перезаписи повреждает переменную.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[12]: Race condition
От: Кодт Россия  
Дата: 05.01.09 18:02
Оценка: 6 (1)
Здравствуйте, Alexander G, Вы писали:

AG>нет, покажи полностью, с самой КС, чтобы было cs.Lock() или EnterCriticalSection(&cs); .

AG>А то непонятно как то что было показано может работать.

Щас попробую на коленке родить.

Сам мьютекс (и все остальные примитивы lock-based синхронизации) нужно инициализировать в рантайме.
Следовательно, мы всё равно хоть что-то, да должны сделать на lock-free.
Поскольку это делается единожды, то нет нужды требовать, чтобы это было ещё и wait-free, так что мы вольны использовать спинлок.

enum InitStatus { NotSet, InProgress, Complete };

template<class Fun>
void do_once(long volatile* status, Fun fun)
{
    switch( InterlockedCompareExchange(status, InProgress, NotSet) )
    {
    case NotSet:
        fun();
        InterlockedExchange(status, Complete);
        return;
    case InProgress:
        while( InterlockedCompareExchange(status, InProgress, InProgress) != Complete )
            Sleep();
        return;
    case Complete:
        return;
    default:
        assert(false);
    }
}

struct StaticCS // POD
{
    long volatile status;
    CRITICAL_SECTION cs;
    
    void init()
    {
        do_once(&status, boost::bind(InitializeCriticalSection, &cs));
    }
    
    void enter()
    {
        init();
        EnterCriticalSection(&cs);
    }
    void leave()
    {
        assert(status==Complete);
        LeaveCriticalSection(&cs);
    }
};
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[3]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 18.01.09 11:48
Оценка: 5 (1)
Здравствуйте, Alexander G, Вы писали:

R>>Ключевой момент здесь — это замена флага initialized на указатель p_data. Это — искуственная инджекция зависимости по данным между загрузкой флага и загрузкой самого объекта. Все современные процессоры автоматически соблюдают упорядочивание при зависимости по данным, в данном случае — между загрузкой указателя и загрузкой данных через этот указатель. Реализация сохранения во флаг тут реализуется так же — т.е. через volatile.


AG>Супер. Только непонятно почему _ReadWriteBarrier();


"Для порядка". Должен был быть и барьер компилятора и аппаратный. Аппаратный убрали, компилятора должен остаться. Хотя, возможно, из-за зависимости по-управлению, он тут и не обязателен, но это такая область, где лучше переборщить, чем недоборщить.

AG>Ещё, можно ли убрать volatile и заменить его на _WriteBarrier(); перед сохранением указателя ?


Нет.
Ну точнее так — на x86 должно работать. А на IA-64 обязателен аппаратный барьер.



R>>Есть ещё вариант с т.н. Lakos Singleton (nifty counting trick). Он инициализируется при инициализации глобальных объектов, однако старается это делать до любого его использования:

AG>...
R>>В принципе, его можно обмануть при желании, поэтому желательно этот заголовочный файл включить во все файлы проекта, stdafx.h подойдёт. Тогда объект будет создан во время инициализации глобальных объектов, однако гарантированно до любого его использования. Плюс не вносит никаких издержек во время выполнения, т.е. никаких дополнительных проверок и индирекций при доступе к объекту.

AG>Если файл включается во все единицы, то всё равно, почему при использование этого из другого глобального объекта (возможно, тоже с внутренним связыванием) не может произойти до создания этого объекта ?


Идея такая, что если какой-то глобальный объект использует наш синглтон, то data.h должен быть подключен в этот файл, и data_initializer_ будет определен в этом файле раньше того глобального объекта.
Хотя, если включаемые файлы подключать не "сверху", а и в середине исходных файлов, то можно его обмануть...


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Lakos Singleton (nifty counting trick).
От: remark Россия http://www.1024cores.net/
Дата: 18.01.09 11:31
Оценка: 1 (1)
Здравствуйте, Alexander G, Вы писали:

R>>В принципе, его можно обмануть при желании, поэтому желательно этот заголовочный файл включить во все файлы проекта, stdafx.h подойдёт. Тогда объект будет создан во время инициализации глобальных объектов, однако гарантированно до любого его использования. Плюс не вносит никаких издержек во время выполнения, т.е. никаких дополнительных проверок и индирекций при доступе к объекту.


AG>Чем это лучше/хуже синглтона Мейерса, форсированного через конструктор глобального объекта с внутренним связыванием ?


Если глобальный объект с внутренним связыванием описать в заголовочном файле, т.е. он попадёт во все единицы трансляции, то на первый взгляд получается примерно то же самое... Хммм... хотя не совсем — в минглтоне Майерса у нас статический объект в функции, соотв. доступ к нему всегда будет иметь некоторый оверхед, как минимум проверку, как максимум какую-то синхронизацию. В синглтоне Лакос мы вольны размещать объект как хочется.

R>>

AG>

1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[19]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 18.01.09 12:16
Оценка: 1 (1)
Здравствуйте, bkat, Вы писали:

AG>>Я действительно хочу много странного, но именно это странным не является. Требование чтобы синглтон работал при конструировании глобальных объектов, в том числе и других синглтонов — стандартное требование. Фактически, самый простой известный синглтон — синглтон Мейерса — решает в первую очередь именно эту задачу — при этом создавая взамен проблемы с многопоточностью и с exception safety.


B>Понятно.

B>У меня просто другая философия.
B>Подобную глобальную инициализацию/деинициализацию предпочитаю делать явной,
B>например, создав специальный объект в main.
B>На зависимостях между глобальными данными уже достаточно много шишек набил.

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


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[5]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 19.01.09 10:04
Оценка: 1 (1)
Здравствуйте, Alexander G, Вы писали:

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


R>>Идея такая, что если какой-то глобальный объект использует наш синглтон, то data.h должен быть подключен в этот файл, и data_initializer_ будет определен в этом файле раньше того глобального объекта.


AG>Раньше определён — раньше создан ? Почему ?


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


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Race condition
От: byleas  
Дата: 02.01.09 17:19
Оценка: -1
Здравствуйте, sc, Вы писали:

sc>Может тогда лучше так?

Это ничего не меняет, в контексте темы.
Re[2]: Race condition
От: Sergey Chadov Россия  
Дата: 03.01.09 10:07
Оценка: +1
Здравствуйте, sc, Вы писали:

sc>Здравствуйте, Alexander G, Вы писали:


AG>>Есть ли тут гонка ?

sc>...
sc>Может тогда лучше так?
sc>
sc>void f() // вызывается из нескольких потоков выполнения одновременно
sc>{
sc>    static long const i = OSVersion(); // возврашает каждый раз одно и то же
sc>  /* дальше используется i. больше нигде она не используется */
sc>}
sc>

sc>Мне кажется, с учетом условий в комментах, гонки нет.

Вот теперь точно есть.
    static long i = OSVersion(); // возврашает каждый раз одно и то же
0041142D  mov         eax,dword ptr [$S1 (41817Ch)] 
00411432  and         eax,1 
00411435  jne         f+6Ch (41145Ch) 
00411437  mov         eax,dword ptr [$S1 (41817Ch)] 
0041143C  or          eax,1 
0041143F  mov         dword ptr [$S1 (41817Ch)],eax 
//тыдымс, переменная "мы проинициализировались" уже true, но сама переменная i еще не проинициализирована
//если сейчас управление получит другой поток - будет ой
00411444  mov         dword ptr [ebp-4],0 
0041144B  call        OSVersion (411104h)
--
Sergey Chadov

... << RSDN@Home 1.2.0 alpha rev. 685>>
Re[3]: Race condition
От: Аноним  
Дата: 03.01.09 20:14
Оценка: +1
Здравствуйте, Alexander G, Вы писали:

AG>Можете обосновать, показать ?


Зависит от платформы. Какая тебя интересует?

А вообще, если хочешь жить спокойно, то тупо сделай одну функцию,
которая гарантированно инициализирует все твои глобальные переменные
(которых много быть не должно), до того, как запустится 2-й поток.
Ты же надеюсь не создаешь потоки при инициализации глобальных данных.
Один из положительных эффектов такого упражнения — ты будешь лучше контролировать глобальные данные,
которые всегда требуют особого внимания.
Re[3]: Race condition
От: Аноним  
Дата: 05.01.09 05:23
Оценка: -1
Здравствуйте, Alexander G, Вы писали:

AG>Задача в том можно ли это сделать работоспособным без блокировок.


Без блокировки никак.
Воткни в main вызов init() до того, как запускаешь потоки и все будет нормально.
Re: Что собственно надо...
От: Alexander G Украина  
Дата: 05.01.09 22:14
Оценка: +1
Наверное таки сформулирую исходную задачу. Чтобы было понятно, почему у меня нет систематического подхода к инициализации объектов.

Я иногда анализирую креш-дампы. Как и автоматически создаваемые и отправляемые пользователями на https://winqual.microsoft.com/ , так и вручную создаваемые через MiniDumpWriteDump. Различные версии Windows дают различные возможности для этого:

Windows 98, ME
Нет ничего. Да и вряд ли стоит стараться для нескольки динозавров.

Windows 2000
Есть DbgHelp.dll . Возможно, той версии, в которой уже есть MiniDumpWriteDump. Так, можно в случае исключения, перехваченного через __except или SetUnhandledExceptionFilter записать креш-дамп. Возможностей по управлению этим крешдампом не очень много, и его стоит делать только в случае продвинутого пользователя, который сам потом его отправит нам и расскажет что за проблема.

Windows XP, 2003
В DbgHelp.dll точно есть MiniDumpWriteDump.
В случае необработанного исключения или зависания пользователь может оправить отчёт в майкрософт. Через тот самый диалог где "Send report" и "Do not send" (жаль что дефолтна последняя). API для управления этим отчётом не предоставляет ничего. Встречаются два вида дампов. Один содержит только эксепшн инфо, стек бектрейс, сегмент данных, инфу по модулям и тредам. Другой — полный дамп кучи, он реже бывает, обычно при зависаниях или по явному требованию после анализа микро-дампов.

Windows Vista, 2008
Имеется полный набор Wer* функций. Можно гра^H^H^H создавать и оправлять свои креш-репорты в winqual, как хочется, включять в них самостоятельно сгенерированные креш-дампы и другие файлы. А ещё можно через функции WerRegisterMemoryBlock, WerRegisterFile, WerSetFlags управлять генерацией автоматически создаваемых креш-дампов. А ещё можно сделать дамп прямо из такск менеджера.

Теперь, как я желаю этим воспользоваться. Во-первых, если надо поддерживать 2000, то всё прийдётся грузить динамически. Далее, дампы, которые делаются вручную, наверное стоит включать только через command-line option, т.к. они не попадут в Winqual и не будут систематизированы, пусть тогда эти дампы отправляются только продвинутыми пользователями по их желанию вместе с описанием проблемы.
Чтобы воспользоваться автоматическими дампами в ХР, неплохо иметь полезную информацию в data segment, т.е. в глобальной переменной. Полезна например будет достаточно свежая MEMORYSTATUS (это API функция вернула нулевой указатель, бо память закончилась — или таки логическая ошибка?). Отсюда требование располагать отладочную структуру в static storage.
В Vista большой интерес представляют WerRegisterMemoryBlock/WerUnregisterMemoryBlock. Можно попробовать зарегистрировать каждый синглтон. Отсюда требование — умение инициализироваться первым. Пока нет полного перехода всех пользователей на Висту, прийдётся находить их динамически из kernel32 (да, самое полезное не в Wer.dll, а в kernel32.dll) через GetProcAddress и иметь стабы для более раних систем. Это — одно из того, что предполагалось инициализировать без синхронизации.
Крешдамп может делаться в условиях испорченной кучи, в условиях нехватки памяти, в условиях переполнения стека. Поэтому сам обработчик UnhandledExceptionFilter пусть вообще использует только глобальные переменные.

Такие дела.
Русский военный корабль идёт ко дну!
Re[5]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 19.01.09 10:09
Оценка: +1
Здравствуйте, Alexander G, Вы писали:

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


R>>"Для порядка". Должен был быть и барьер компилятора и аппаратный. Аппаратный убрали, компилятора должен остаться. Хотя, возможно, из-за зависимости по-управлению, он тут и не обязателен, но это такая область, где лучше переборщить, чем недоборщить.


AG>Может у меня не хватает фантазии, но я не редставляю, как компилятор может испортить. Мне кажется, что такое


AG>при этом барьер на патологический случай, если копилятор вздумает делать что-то до проверки указателя, а потом, при успешной проверке, отменять что наделал.


А смысл, если _ReadWriteBarrier() ничего не стОит в ран-тайм?


AG>>>Ещё, можно ли убрать volatile и заменить его на _WriteBarrier(); перед сохранением указателя ?


R>>Нет.

R>>Ну точнее так — на x86 должно работать. А на IA-64 обязателен аппаратный барьер.

AG>http://msdn.microsoft.com/en-us/library/f20w0x5e(VS.80).aspx

AG>

AG>Marking memory with a memory barrier is similar to marking memory with the volatile (C++) keyword.

AG>?

У них, честно говоря, такая дерьмовая документация, что уже отчаялся в ней что-либо понять. Нет, что б явно и чётко всё написать, всё какими-то намёками выражаются...
По моим представлениям, если бы эти _ReadWriteBarrier/_WriteBarrier/_ReadBarrier были бы и аппаратными барьерами, то на x86 _ReadWriteBarrier должен был бы выливаться в инструкцию mfence. Этого не происходит. Поэтому я считаю, что это — только барьеры компилятора... Хотя интересно было бы поглядеть как _WriteBarrier компилируется на IA-64 — я не знаю...



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Race condition
От: Alexander G Украина  
Дата: 02.01.09 10:02
Оценка:
Есть ли тут гонка ?

long i; // глобальная переменная

void f() // вызывается из нескольких потоков выполнения одновременно
{
  i = OSVersion(); // возврашает каждый раз одно и то же
  /* дальше используется i. больше нигде она не используется */
}
Русский военный корабль идёт ко дну!
Re: Race condition
От: sc Россия  
Дата: 02.01.09 10:32
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Есть ли тут гонка ?

...
Может тогда лучше так?
void f() // вызывается из нескольких потоков выполнения одновременно
{
    static long const i = OSVersion(); // возврашает каждый раз одно и то же
  /* дальше используется i. больше нигде она не используется */
}

Мне кажется, с учетом условий в комментах, гонки нет.
Re: Race condition
От: Аноним  
Дата: 02.01.09 12:30
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Есть ли тут гонка ?


Да, есть.
Re[2]: Race condition
От: sc Россия  
Дата: 02.01.09 14:15
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, Alexander G, Вы писали:


AG>>Есть ли тут гонка ?


А>Да, есть.


А почему? Вот одно из определений race condition.

A race condition is a flaw in a system or process whereby the output and/or result of the process is unexpectedly and critically dependent on the sequence or timing of other events.

Мой, краткий перевод: Race condition — ошибка из-за зависимости результата от последовательности событий.

Значение i везде одинаково и не меняется, значит нет. Конечно, если топикстартер под словом используется понимает и изменяется, то тогда да, есть.
Re[3]: Race condition
От: Аноним  
Дата: 03.01.09 08:19
Оценка:
Здравствуйте, byleas, Вы писали:

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


sc>>Может тогда лучше так?

B>Это ничего не меняет, в контексте темы.

Ну почему же?
Гонка становится более очевидной,
если прикинуть, как инициируются подобные статические переменные
Re[4]: Race condition
От: sc Россия  
Дата: 03.01.09 08:57
Оценка:
Здравствуйте, Аноним, Вы писали:

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


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


sc>>>Может тогда лучше так?

B>>Это ничего не меняет, в контексте темы.

А>Ну почему же?

А>Гонка становится более очевидной,
А>если прикинуть, как инициируются подобные статические переменные

И как они инициируются? Хотелось бы на примере увидеть.
Re[2]: Race condition
От: Alexander G Украина  
Дата: 03.01.09 10:16
Оценка:
Здравствуйте, Аноним, Вы писали:

AG>>Есть ли тут гонка ?


А>Да, есть.


Можете обосновать, показать ?
Русский военный корабль идёт ко дну!
Re[3]: Race condition
От: sc Россия  
Дата: 03.01.09 10:47
Оценка:
Здравствуйте, Sergey Chadov, Вы писали:

SC>Вот теперь точно есть.

SC>
SC>    static long i = OSVersion(); // возврашает каждый раз одно и то же
SC>0041142D  mov         eax,dword ptr [$S1 (41817Ch)] 
SC>00411432  and         eax,1 
SC>00411435  jne         f+6Ch (41145Ch) 
SC>00411437  mov         eax,dword ptr [$S1 (41817Ch)] 
SC>0041143C  or          eax,1 
SC>0041143F  mov         dword ptr [$S1 (41817Ch)],eax 
SC>//тыдымс, переменная "мы проинициализировались" уже true, но сама переменная i еще не проинициализирована
SC>//если сейчас управление получит другой поток - будет ой
SC>00411444  mov         dword ptr [ebp-4],0 
SC>0041144B  call        OSVersion (411104h) 
SC>


Спасибо, не знал, что со статиком такая засада может быть. Век живи — век учись
Re[5]: Race condition
От: Аноним  
Дата: 03.01.09 20:19
Оценка:
Здравствуйте, sc, Вы писали:

sc>И как они инициируются? Хотелось бы на примере увидеть.


Тебе уже ответили.
Статические переменные по типу, как ты предложил, в С++ в принципе создают кучу неприятностей
в многопоточном приложении.
Глобальные данные в этом смысле даже безопаснее, поскольку их можно
попытаться инициировать до запуска дополнительных потоков.
Re[4]: Race condition
От: Alexander G Украина  
Дата: 03.01.09 20:38
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Зависит от платформы. Какая тебя интересует?


MSVC8 (2005) Windows XP Vista x86 x64

А>А вообще, если хочешь жить спокойно, то тупо сделай одну функцию,

А>которая гарантированно инициализирует все твои глобальные переменные
А>(которых много быть не должно), до того, как запустится 2-й поток.
А>Ты же надеюсь не создаешь потоки при инициализации глобальных данных.
А>Один из положительных эффектов такого упражнения — ты будешь лучше контролировать глобальные данные,
А>которые всегда требуют особого внимания.

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

Я вот что не понимаю. Допустим первый поток изменил переменную с 0 до 6. Далее он видит только 6. Последующие потоки будут менять с 6 до 6, т.е. изменений не будет. Где ошибки ?
Русский военный корабль идёт ко дну!
Re[5]: Race condition
От: Аноним  
Дата: 03.01.09 20:57
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Я вот что не понимаю. Допустим первый поток изменил переменную с 0 до 6. Далее он видит только 6. Последующие потоки будут менять с 6 до 6, т.е. изменений не будет. Где ошибки ?


Ну например ошибка в том, что ты думаешь, что операция "первый поток изменил переменную с 0 до 6" — атомарна.
Кстати, меняешь ты не с 0 а со случайной величины.
Re[6]: Race condition
От: Alexander G Украина  
Дата: 03.01.09 21:14
Оценка:
Здравствуйте, Аноним, Вы писали:

AG>>Я вот что не понимаю. Допустим первый поток изменил переменную с 0 до 6. Далее он видит только 6. Последующие потоки будут менять с 6 до 6, т.е. изменений не будет. Где ошибки ?


А>Ну например ошибка в том, что ты думаешь, что операция "первый поток изменил переменную с 0 до 6" — атомарна.


И что из не-атомарности следует ?

А>Кстати, меняешь ты не с 0 а со случайной величины.


Точно ? Вроде как:

3.8/6
The memory occupied by any object of static storage duration shall be
zero-initialized at program startup before any other initialization
takes place. [Note: in some cases, additional initialization is done
later. ]

Русский военный корабль идёт ко дну!
Re: Race condition
От: Кодт Россия  
Дата: 04.01.09 22:29
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Есть ли тут гонка ?


Если sizeof(long)>sizeof(int), то, скорее всего, запись будет неатомарна — в том смысле, что другой поток, не позаботившийся о вызове f(), может прочесть комбинацию из половинки OSVersion и нуля.

Если же каждый поток обязательно вызывает f(), то нужно иметь особо извращённую платформу, на которой перезапись того же самого значения проходит через промежуточную порчу переменной.

Но кстати, раз f() вызывается в каждом потоке — так, может быть, стоит использовать TLS?
Для MS-specific есть __declspec(thread), для других компиляторов надо читать документацию. Или вызывать соответствующие функции ОС.
Перекуём баги на фичи!
Re[2]: Race condition
От: Alexander G Украина  
Дата: 04.01.09 23:11
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Если sizeof(long)>sizeof(int), то, скорее всего, запись будет неатомарна — в том смысле, что другой поток, не позаботившийся о вызове f(), может прочесть комбинацию из половинки OSVersion и нуля.

К>Если же каждый поток обязательно вызывает f(), то нужно иметь особо извращённую платформу, на которой перезапись того же самого значения проходит через промежуточную порчу переменной.

Вот. Хотелось бы подробнее про эту платформу
Также интересно, как в ней повлияет замена присвоения на memcpy. И замена long на long volatile. И замена long на bool.

К>Но кстати, раз f() вызывается в каждом потоке — так, может быть, стоит использовать TLS?


На самом деле задача ставится так. Есть глобальная структура, инициализировванная статичeски:
struct data_t
{
  void * p1;
  void * p2;
  int  i3;
  ...
  bool initialized;
} data = {0};


Есть функция для инициализации, которая может быть в редких случаях вызвана из нескольки потоков одновременно:
void init()
{
  if (initialized)
    return;
  data.p1 = ::GetModuleHandle(...);
  data.p2 = ::GetProcAddress(...);
  data.i3 = (int)sqrt(42*_WIN_VER);
  initialized = true;
}

Т.е никакая не init-once, скорее init-Nce

Задача в том можно ли это сделать работоспособным без блокировок.
Русский военный корабль идёт ко дну!
Re[3]: Race condition
От: Кодт Россия  
Дата: 05.01.09 11:45
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Т.е никакая не init-once, скорее init-Nce

AG>Задача в том можно ли это сделать работоспособным без блокировок.

Если ты мамой клянёшься, что пишешь туда всегда одно и то же, то гонки здесь не возникают.
Разве что для надёжности можешь заменить неатомарные присваивания атомарными — InterlockedExchange / InterlockedCompareExchange.

Ещё можно подумать о проверках, скажем, таких
void perfect_assign(long& var, long value)
{
    long old = InterlockedCompareExchange(&var, value, 0); // перезаписываем, только если там 0
    assert(old == 0 || old == value);
}

long perfect_read(long& var) // для проверки initialized
{
    return InterlockedCompareExchange(&var, 0, 0); // единственная засада - это создаётся барьер памяти
}


А заморачиваться в сторону универсальных lock-free алгоритмов... Ну, можно, конечно, но в твоей постановке задачи не нужно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[4]: Race condition
От: bkat  
Дата: 05.01.09 11:59
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Если ты мамой клянёшься, что пишешь туда всегда одно и то же, то гонки здесь не возникают.


А какая разница, что он туда пишет?
Достаточно уже этого:
  if (initialized)
    return;
  // тут даже не важно, что вообще инициируется
  initialized = true;

чтобы подумать о нормальной инициализации.

А мамой можно клянуться сколько угодно, но этих клятв хватает максимум до следующего релиза
Re[4]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 12:00
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Разве что для надёжности можешь заменить неатомарные присваивания атомарными — InterlockedExchange / InterlockedCompareExchange.


InterlockedExchange тоже не хочу, прийдётся писать volatile и терять возможную оптимизацию при дальнейшем использовании. А что будет значить "для надёжности" ?
Русский военный корабль идёт ко дну!
Re[5]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 12:42
Оценка:
Здравствуйте, bkat, Вы писали:

B>Достаточно уже этого:

B>
B>  if (initialized)
B>    return;
B>  // тут даже не важно, что вообще инициируется
B>  initialized = true;
B>

B>чтобы подумать о нормальной инициализации.

Давай подумаем о нормальной инициализации.
Требования:
1. Возможность вызова до main. Желательно чтобы была такая защищённость от дурака, чтобы работало даже если дополнительные потоки созданы до main.
2. Максимальная простота.
3. Максимальная переносимость.
4. Расположение в статической памяти.

Поэтому, откинем все эти boost::call_once или InitOnceXxx, синглтон Мейерса, другие известные реализации синглтона... и вроде ничего "нормального" не остаётся. И я не против если не нормальное оказывается лучше.

B>А мамой можно клянуться сколько угодно, но этих клятв хватает максимум до следующего релиза


Как бы эта штука вообще меняется в первый раз. И там нет ничего, что может нарушать клятву о полной повторимости каждой инициализации.
Русский военный корабль идёт ко дну!
Re[6]: Race condition
От: bkat  
Дата: 05.01.09 13:30
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


B>>Достаточно уже этого:

B>>
B>>  if (initialized)
B>>    return;
B>>  // тут даже не важно, что вообще инициируется
B>>  initialized = true;
B>>

B>>чтобы подумать о нормальной инициализации.

AG>Давай подумаем о нормальной инициализации.

AG>Требования:
AG>1. Возможность вызова до main. Желательно чтобы была такая защищённость от дурака, чтобы работало даже если дополнительные потоки созданы до main.
AG>2. Максимальная простота.
AG>3. Максимальная переносимость.
AG>4. Расположение в статической памяти.

AG>Поэтому, откинем все эти boost::call_once или InitOnceXxx, синглтон Мейерса, другие известные реализации синглтона... и вроде ничего "нормального" не остаётся.


Как раз банальный синглтон, в котором бы честно синхронизировалось создание экземпляра,
было бы, на мой взгляд, простым решением.
Пусть твой синглтон и отвечает за такие вещи, как версия OS и прочие подобные вещи.
Проблема то стандартная. Чего тут то на блокировках экономить?

B>>И я не против если не нормальное оказывается лучше.

Лучше чего? Твое решение — это просто инициализация с полным игнорированием проблем многопоточных приложений.
Ну блокировки нет, зато гонки есть, и есть большая вероятность начать пользоваться
данными до того, как кто-то их проинициировал.

B>>А мамой можно клянуться сколько угодно, но этих клятв хватает максимум до следующего релиза


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


Ну тебе еще придется давать клятву о том, что каждый поток (включая главный поток) гарантированно вызывает
функцию init() до того, как он попытается использовать результаты инициализации.
Кстати, это может запросто случится даже без дополнительных потоков.
С учетом того, что глобальные данные в различных модулях инициализируются
в непредсказуемом порядке, ты можешь поиметь очень забавные побочные эффекты.
Re[5]: Race condition
От: bkat  
Дата: 05.01.09 13:31
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>InterlockedExchange тоже не хочу, прийдётся писать volatile и терять возможную оптимизацию при дальнейшем использовании. А что будет значить "для надёжности" ?


Похоже, что ты экономишь даже не на спичках, а огрызках от спичек.
Re[7]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 13:59
Оценка:
Здравствуйте, bkat, Вы писали:


B>Как раз банальный синглтон, в котором бы честно синхронизировалось создание экземпляра,

B>было бы, на мой взгляд, простым решением.

С помощью какого примитива синхронизации ?

B>Пусть твой синглтон и отвечает за такие вещи, как версия OS и прочие подобные вещи.

B>Проблема то стандартная. Чего тут то на блокировках экономить?

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

Вроде придумал так:

struct data_t
{
  ...
} data = {0};

long volatile initialized = FALSE;

void init()
{
  if (!initialized)
    return;
  data_t local_data;
  /* инициализация local_data */
  ...
  if (::InterlockedExchange(&initialized, TRUE))
    memcpy(&data, local_data, sizeof(data_t);
}


B>Ну тебе еще придется давать клятву о том, что каждый поток (включая главный поток) гарантированно вызывает

B>функцию init() до того, как он попытается использовать результаты инициализации.
B>Кстати, это может запросто случится даже без дополнительных потоков.

Это детали реализации, наружу будет немного методов, init в каждом из которых. Или наружу можно даже типа синглтона, с методом Instance.
Русский военный корабль идёт ко дну!
Re[6]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 14:05
Оценка:
Здравствуйте, bkat, Вы писали:

B>Похоже, что ты экономишь даже не на спичках, а огрызках от спичек.


Я знаю на что это похоже. volatile был бы добавлен если было бы известно что без него не работает, а с ним работает.
Русский военный корабль идёт ко дну!
Re[8]: Race condition
От: bkat  
Дата: 05.01.09 14:31
Оценка:
Здравствуйте, Alexander G, Вы писали:

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



B>>Как раз банальный синглтон, в котором бы честно синхронизировалось создание экземпляра,

B>>было бы, на мой взгляд, простым решением.

AG>С помощью какого примитива синхронизации ?

Да хоть с критической секцией.

B>>Пусть твой синглтон и отвечает за такие вещи, как версия OS и прочие подобные вещи.

B>>Проблема то стандартная. Чего тут то на блокировках экономить?

AG>Тут такое дело, что желательно избавиться от каждого вызова апи иоли библиотеки.


AG>Вроде придумал так:


AG>
AG>struct data_t
AG>{
AG>  ...
AG>} data = {0};

AG>long volatile initialized = FALSE;

AG>void init()
AG>{
AG>  if (!initialized)
AG>    return;
AG>  data_t local_data;
AG>  /* инициализация local_data */
AG>  ...
AG>  if (::InterlockedExchange(&initialized, TRUE))
AG>    memcpy(&data, local_data, sizeof(data_t);
AG>}
AG>


Плохо, очень плохо.
Ты можешь с таким же успехом выкинуть InterlockedExchange и это будет так же "надежно", как и твой вариант.
В момент, когда работает memcpy, для других потоков данные уже инициированы, хотя это не так.

Сделай лучше для начала типа этого:
struct data_t
{
  static data_t* getInstance()
  {
    lock(); // например с помощью мьютекса
    static data_t* instance = new data_t;
    unlock();
    return instance;
  }
};


Если getInstance() безбожно будет тормозить твою систему (в чем я сильно сомневаюсь),
то просто вызывай getInstance один раз на поток.
Будь проще, и не пытайся оптимизировать то, что скорей всего никогда не будет тормозить.
Re[9]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 15:45
Оценка:
Здравствуйте, bkat, Вы писали:

AG>>С помощью какого примитива синхронизации ?

B>Да хоть с критической секцией.

Покажи работающий синглтон с критичесской секцией
Это нетривиально.

B>>>Пусть твой синглтон и отвечает за такие вещи, как версия OS и прочие подобные вещи.

B>>>Проблема то стандартная. Чего тут то на блокировках экономить?

AG>>Тут такое дело, что желательно избавиться от каждого вызова апи иоли библиотеки.


AG>> if (::InterlockedExchange(&initialized, TRUE))

AG>> memcpy(&data, local_data, sizeof(data_t);
AG>>}
AG>>[/ccode]

B>Плохо, очень плохо.


Понял, хуже чем было
Русский военный корабль идёт ко дну!
Re[10]: Race condition
От: bkat  
Дата: 05.01.09 15:50
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


AG>>>С помощью какого примитива синхронизации ?

B>>Да хоть с критической секцией.

AG>Покажи работающий синглтон с критичесской секцией

AG>Это нетривиально.

Дак я тебе же показал.
Нетривиально — это для тех, кто не хотят пользоваться критическими секциями.
Ты наверное имеешь ввиду дискуссии на тему типа этой:
http://ru.wikipedia.org/wiki/Double_checked_locking

А то, что я тебе показал — это примитивное решение "в лоб", которое работает
Re[11]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 15:56
Оценка:
Здравствуйте, bkat, Вы писали:

B>Дак я тебе же показал.

B>Нетривиально — это для тех, кто не хотят пользоваться критическими секциями.

нет, покажи полностью, с самой КС, чтобы было cs.Lock() или EnterCriticalSection(&cs); .
А то непонятно как то что было показано может работать.
Русский военный корабль идёт ко дну!
Re[13]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 16:16
Оценка:
Здравствуйте, bkat, Вы писали:

B>Ну блин...

B>Народ с основами не разобрался, а уже многопоточные приложения пишет

B>Вот, как бы это выглядело бы на Qt


B>
B>struct data_t
B>{
B>  static data_t* getInstance()
B>  {
B>    QMutexLocker locker(&mutex);
B>    static data_t* instance = new data_t;
B>    return instance;
B>  }
B>  static QMutex mutex;
B>};
B>


B>Считай, QMutex в конструкторе инициализирует CRITICAL_SECTION

B>QMutexLocker в конструкторе вызывает EnterCriticalSection,
B>а в деструкторе — LeaveCriticalSection.

Этот код не рабочий, т.к. getInstance может быть вызван до конструктора QMutex.
Русский военный корабль идёт ко дну!
Re[14]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 16:19
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Этот код не рабочий, т.к. getInstance может быть вызван до конструктора QMutex.


Да, возможно QMutex не имеет конструктора, тогда код рабочий.
Но — увы — виндовая КС должна инициализироваться InitializeCriticalSection
Русский военный корабль идёт ко дну!
Re[16]: Race condition
От: bkat  
Дата: 05.01.09 17:15
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Если бы всё было так просто, я бы действительно взял КС.


Все время забываю, что тебе нужна гарантия в случае если
кто-то пожелает вызвать getInstance при создании глобальных объектов.

Тут извини. Я пас...
Чем-то придется пожертвовать.
Re[13]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 19:00
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Щас попробую на коленке родить.


К>Сам мьютекс (и все остальные примитивы lock-based синхронизации) нужно инициализировать в рантайме.

К>Следовательно, мы всё равно хоть что-то, да должны сделать на lock-free.
К>Поскольку это делается единожды, то нет нужды требовать, чтобы это было ещё и wait-free, так что мы вольны использовать спинлок.

...

Так, это уже лучше чем КС с конструктором

пара вопросов:
1. Нужна ли мне КС или инициализировать через do_once саму data_t — какие будут практические рекомендации для выбора ?
2. А вот ты бы оставил "опасный" вариант или делал бы настоящий do_once вроде этого ?
Русский военный корабль идёт ко дну!
Re[17]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 19:22
Оценка:
Здравствуйте, bkat, Вы писали:

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


AG>>Если бы всё было так просто, я бы действительно взял КС.


B>Все время забываю, что тебе нужна гарантия в случае если

B>кто-то пожелает вызвать getInstance при создании глобальных объектов.

Я действительно хочу много странного, но именно это странным не является. Требование чтобы синглтон работал при конструировании глобальных объектов, в том числе и других синглтонов — стандартное требование. Фактически, самый простой известный синглтон — синглтон Мейерса — решает в первую очередь именно эту задачу — при этом создавая взамен проблемы с многопоточностью и с exception safety.

Если бы существование при инициализации не было бы нужно, я бы просто сделал инициализацию в конструкторе глобального объекта. Можно конечно сделать что-то вроде
struct T{
  static T& instance() {
    static T t;
    return t;
  }
};

struct T_init {
  T_init() { T::instance(); }
} const t_init;

, но у меня в требованиях
Автор: Alexander G
Дата: 05.01.09
 есть "желательно".
Русский военный корабль идёт ко дну!
Re[14]: Race condition
От: Кодт Россия  
Дата: 05.01.09 19:32
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>пара вопросов:

AG>1. Нужна ли мне КС или инициализировать через do_once саму data_t — какие будут практические рекомендации для выбора ?
AG>2. А вот ты бы оставил "опасный" вариант или делал бы настоящий do_once вроде этого ?

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


Раз у тебя загрузка модуля отложенная, то, наверно, я бы сделал так:
1) Процедуру загрузки — атомарной
2) Атомарность — lock-based (просто потому, что это проще для понимания и раздачи всяких гарантий)
3) Инфраструктуру для атомарности — т.е. мьютекс aka CRITICAL_SECTION — инициализировал бы отдельно (или заранее, в конструкторе статического объекта, или на механизме do_once)
4) Процедуру чтения — тут есть два варианта.
— Либо потребовать, чтобы загрузка была выполнена обязательно до использования, и тогда плевать на атомарность (мы же только читаем)
— Либо загрузка по требованию, каждый раз, и тогда для ускорения прикрутил бы внутрь процедуры загрузки ещё и двойную проверку.

Тут главное, чтобы каждая подзадача была прозрачна.

Ещё я бы постарался сгруппировать все эти загрузки деталек из модуля в одно целое, чтобы не городить по синглетону на каждую импортируемую функцию.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[18]: Race condition
От: bkat  
Дата: 05.01.09 19:39
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Я действительно хочу много странного, но именно это странным не является. Требование чтобы синглтон работал при конструировании глобальных объектов, в том числе и других синглтонов — стандартное требование. Фактически, самый простой известный синглтон — синглтон Мейерса — решает в первую очередь именно эту задачу — при этом создавая взамен проблемы с многопоточностью и с exception safety.


Понятно.
У меня просто другая философия.
Подобную глобальную инициализацию/деинициализацию предпочитаю делать явной,
например, создав специальный объект в main.
На зависимостях между глобальными данными уже достаточно много шишек набил.
Re[15]: Race condition
От: Alexander G Украина  
Дата: 05.01.09 22:28
Оценка:
Здравствуйте, Кодт, Вы писали:

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


Не, оно именно работает без детерминизма, вне зависимости от остальной программы, и фича — пытаться работать даже если у меня ошибка синхронизации. Я детализировал
Автор: Alexander G
Дата: 06.01.09
.
Русский военный корабль идёт ко дну!
Re[13]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 16.01.09 17:36
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Сам мьютекс (и все остальные примитивы lock-based синхронизации) нужно инициализировать в рантайме.

К>Следовательно, мы всё равно хоть что-то, да должны сделать на lock-free.

Это только в мире Windows, в мире POSIX (или pthread-win32) мы можем просто написать:
pthread_mutex_t g_guard = PTHREAD_MUTEX_INITIALIZER;

в мире boost:
boost::detail::basic_recursive_mutex g_guard = BOOST_BASIC_RECURSIVE_MUTEX_INITIALIZER;



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Race condition
От: Alexander G Украина  
Дата: 16.01.09 20:51
Оценка:
Здравствуйте, remark, Вы писали:

R>Microsoft явно не документирует модель памяти Windows, хотя, видимо, подоплёка такая же как и в POSIX. Поэтому тоже формально гонка и UB.


В MSDN Library нашел, как мне кажется, достаточную для моего случая гарантию:
http://msdn.microsoft.com/en-us/library/ms684122.aspx

Simple reads and writes to properly-aligned 32-bit variables are atomic operations. In other words, you will not end up with only one portion of the variable updated; all bits are updated in an atomic fashion. However, access is not guaranteed to be synchronized. If two threads are reading and writing from the same variable, you cannot determine if one thread will perform its read operation before the other performs its write operation.

Simple reads and writes to properly aligned 64-bit variables are atomic on 64-bit Windows. Reads and writes to 64-bit values are not guaranteed to be atomic on 32-bit Windows. Reads and writes to variables of other sizes are not guaranteed to be atomic on any platform.


R>
R>struct data_t
R>{
R>  void * p1;
R>  void * p2;
R>  int  i3;
R>  ...
R>} data = {0};

R>data_t* p_data = 0;

R>data_t* init()
R>{
R>    data_t* p = p_data;
R>    _ReadWriteBarrier();
R>    if (p)
R>        return p;
R>    data.p1 = ::GetModuleHandle(...);
R>    data.p2 = ::GetProcAddress(...);
R>    data.i3 = (int)sqrt(42*_WIN_VER);
R>    *((data_t* volatile*)(&p_data)) = &data;
R>    return p_data;
R>}
R>


R>Ключевой момент здесь — это замена флага initialized на указатель p_data. Это — искуственная инджекция зависимости по данным между загрузкой флага и загрузкой самого объекта. Все современные процессоры автоматически соблюдают упорядочивание при зависимости по данным, в данном случае — между загрузкой указателя и загрузкой данных через этот указатель. Реализация сохранения во флаг тут реализуется так же — т.е. через volatile.


Супер. Только непонятно почему _ReadWriteBarrier();
Ещё, можно ли убрать volatile и заменить его на _WriteBarrier(); перед сохранением указателя ?

R>Хотя, в принципе, конечно, желательно кодировать не против конкретной платформы, а против портабельного стандарта; и спрашивать не "почему это может не работать?", а "кем гарантировано, что это должно работать?" (я имею в виду использование мьютексов и т.д).


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

R>Есть ещё вариант с т.н. Lakos Singleton (nifty counting trick). Он инициализируется при инициализации глобальных объектов, однако старается это делать до любого его использования:

...
R>В принципе, его можно обмануть при желании, поэтому желательно этот заголовочный файл включить во все файлы проекта, stdafx.h подойдёт. Тогда объект будет создан во время инициализации глобальных объектов, однако гарантированно до любого его использования. Плюс не вносит никаких издержек во время выполнения, т.е. никаких дополнительных проверок и индирекций при доступе к объекту.

Если файл включается во все единицы, то всё равно, почему при использование этого из другого глобального объекта (возможно, тоже с внутренним связыванием) не может произойти до создания этого объекта ?

R>

Русский военный корабль идёт ко дну!
Re[15]: Race condition
От: Alexander G Украина  
Дата: 16.01.09 21:47
Оценка:
Здравствуйте, alsemm, Вы писали:

A>to Alexander G: вас спасут именованные мьютексы. Если из нескольких потоков запрашивать создать мьютекс с одним и тем же именем, то создан будет он только в одном потоке, а в остальные будет возвращен handle созданного мьютекса.


Интересно.
Для моего случая видимо стоит делать мьютекс так, чтобы он не был один на все копии процесса:

TCHAR mutexName[50];
_tprintf_s(mutex, _T("Local\\8E6EB8AF-5DA9-4667-B922-E10F7CA64F62_%d"), ::GetCurrentProcessId());
HANDLE guard = CreateMutex(0, FALSE, mutexName);


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

A>т.к. если память выделенную для globals_ система может и почистит сама, после завершения приложения, то именованный мьютекс — вряд-ли.


Очистит, и, если им больше никто не владеет, удалит.

Можно попробовать в одном процессе создать, занять и не вернуть, а в другом создать и ждать, при завершении первого во втором д.б. WAIT_ABANDONED.
Русский военный корабль идёт ко дну!
Re[15]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 17.01.09 09:38
Оценка:
Здравствуйте, alsemm, Вы писали:

R>>Это только в мире Windows, в мире POSIX (или pthread-win32) мы можем просто написать:

R>>
R>>pthread_mutex_t g_guard = PTHREAD_MUTEX_INITIALIZER;
R>>

R>>в мире boost:
R>>
R>>boost::detail::basic_recursive_mutex g_guard = BOOST_BASIC_RECURSIVE_MUTEX_INITIALIZER;
R>>

A>Это все работать будет, если только все эти красивые макросы разворачиваются в константы.

Именно. Они в константы и будут всегда разворачиваться.


A>В MS Windows никаких констант нет, значит инициализация g_guard будет не на этапе загрузки секций кода из exe в память, а позже, когда возможно уже какие-то глобальные функции вызываются.


В Windows можно использовать boost или pthread-win32.
А нужно именно Win32 API, то InitOnce. Если до Vista, то ничего хорошего для этой цели нет — либо самому морочиться с Interlocked операциями, либо premature pessimization в виде именованных объектов ядра для межпроцессного взаимодействия.



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[15]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 17.01.09 10:05
Оценка:
Здравствуйте, alsemm, Вы писали:

A>to Alexander G: вас спасут именованные мьютексы. Если из нескольких потоков запрашивать создать мьютекс с одним и тем же именем, то создан будет он только в одном потоке, а в остальные будет возвращен handle созданного мьютекса.


A>Как-то так:

A>
A>class Globals
A>{
A>    static Globals* instance_;
A>public:
A>    Globals*
A>    getInstance() // nothrow
A>    {
A>        HANDLE guard = CreateMutex(0, false, "unique-name-of-your-taste");
A>        if (NULL == guard)
A>        {
A>            return 0;
A>        }

A>        if (WAIT_FAILED == ::WaitForSingleObject(impl, INFINITE))
A>        {
A>            return 0;
A>        }

A>        if (0 == globals_)
A>        {
A>            globals_ = new (std::nothrow) Globals();
A>        }

A>        ::ReleaseMutex(guard);

A>        return globals_;
A>    }
A>};
A>


A>По хорошему надо еще где-то

A>
A>delete globals_;
A>::CloseHandle(<тот-самый мьютекс>);
A>

A>делать, т.к. если память выделенную для globals_ система может и почистит сама, после завершения приложения, то именованный мьютекс — вряд-ли.

Не считая того, что получаем 3 системных вызова вместо + блокировку вместо пары инструкций процессора...
Если при каждом получении синглтона создавать по хендлу, то через пару минут лимит хендлов будет достигнут и новые хендлы на event не будут создаваться — будем получать 0 вместо синглтона.
Плюс new (std::nothrow) не работает так, как ты думаешь, соотв. будем получать полное зависание процесса при исключении из new (std::nothrow).
Плюс CloseHandle() надо делать на каждый CreateMutex(), а не один раз. Один CloseHandle() ничего не закроет, т.к. хэндлов насоздавалась уже целая туча.
Плюс ОС сама разрушит event при завершении процесса.


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

A>>В MS Windows никаких констант нет, значит инициализация g_guard будет не на этапе загрузки секций кода из exe в память, а позже, когда возможно уже какие-то глобальные функции вызываются.


R>В Windows можно использовать boost или pthread-win32.

Там полностью свои сампописные муьютексы/крит. секции?

R>А нужно именно Win32 API, то InitOnce. Если до Vista, то ничего хорошего для этой цели нет — либо самому морочиться с Interlocked операциями,

А чего с ними мучаться — взять просто boost::detail::spinlock и все дела.

R>либо premature pessimization в виде именованных объектов ядра для межпроцессного взаимодействия.

Ну getInstance() — это не тот случай, тобы такты экономить, не часто она и вызываться-то будет.

Алексей

R>
Re[16]: Race condition
От: alsemm Россия  
Дата: 17.01.09 12:28
Оценка:
Здравствуйте, remark, Вы писали:

R>Не считая того, что получаем 3 системных вызова вместо + блокировку вместо пары инструкций процессора...

R>Если при каждом получении синглтона создавать по хендлу, то через пару минут лимит хендлов будет достигнут и новые хендлы на event не будут создаваться — будем получать 0 вместо синглтона.
Мой косяк, согласен. См. ниже.

R>Плюс new (std::nothrow) не работает так, как ты думаешь, соотв. будем получать полное зависание процесса при исключении из new (std::nothrow).

Замечание по существу, но ыглядит как придирка, т.к. ничего не мешает или 'new Globals();' в try/catch обернуть, или просто принять, что конструктор Globals не должен кидать исключений.

R>Плюс CloseHandle() надо делать на каждый CreateMutex(), а не один раз. Один CloseHandle() ничего не закроет, т.к. хэндлов насоздавалась уже целая туча.

Да нет проблем:
HANDLE guard = CreateMutex(0, false, "unique-name-of-your-taste");
if (NULL == guard)
{
    return 0;
}
const bool closeHandle = (ERROR_ALREADY_EXISTS == ::GetLastError());
...
if (closeHandle)
{
    CloseHandle(guard);
}


R>Плюс ОС сама разрушит event при завершении процесса.

Так это ж хорошо.

Алексей
Re[16]: Race condition
От: alsemm Россия  
Дата: 17.01.09 12:39
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


A>>to Alexander G: вас спасут именованные мьютексы. Если из нескольких потоков запрашивать создать мьютекс с одним и тем же именем, то создан будет он только в одном потоке, а в остальные будет возвращен handle созданного мьютекса.


AG>Интересно.

AG>Для моего случая видимо стоит делать мьютекс так, чтобы он не был один на все копии процесса:
Ну не знаю, Globals::globals_-то у вас одна на все процессы будет, если она в dll. А если в exe, то да, нужно имя мьютекса уникальное для каждого процесса заводить.

AG>Но мне больше нравится ответ remark, что можно и без мьютекса, если обеспечить правильный запрет переупорядочивания.

"Скользкое решение" imho, хотя и довольно элегантное. С именованным мьтексом выглядит брутально, но зато все очевидно. Кстати в pthread-ах именованные мьютексы тоже есть, это к вопросу о переносимости.

AG>Можно попробовать в одном процессе создать, занять и не вернуть, а в другом создать и ждать, при завершении первого во втором д.б. WAIT_ABANDONED.

А это для чего?

Алексей
Re[17]: Race condition
От: Alexander G Украина  
Дата: 17.01.09 12:43
Оценка:
Здравствуйте, alsemm, Вы писали:

AG>>Можно попробовать в одном процессе создать, занять и не вернуть, а в другом создать и ждать, при завершении первого во втором д.б. WAIT_ABANDONED.

A>А это для чего?

Это просто к тому, что при аварийном завершении процесса ничего страшного не произойдёт.
Русский военный корабль идёт ко дну!
Re[17]: Race condition
От: Alexander G Украина  
Дата: 17.01.09 12:51
Оценка:
Здравствуйте, alsemm, Вы писали:

AG>>Интересно.

AG>>Для моего случая видимо стоит делать мьютекс так, чтобы он не был один на все копии процесса:
A>Ну не знаю, Globals::globals_-то у вас одна на все процессы будет, если она в dll. А если в exe, то да, нужно имя мьютекса уникальное для каждого процесса заводить.

не уловил, какая разница dll/exe, если синглтон один штук на одно адресное пространство в обоих случаях.

В случае dll вообще прийдётся забыть про Win 2000, которая интересует многих
Автор: Alexander G
Дата: 30.12.08
Вопрос: Для какой наиболее ранней версии Windows 9x/NT должно работать разрабатываемое вами ПО ?
.

http://msdn.microsoft.com/en-us/library/ms682583(VS.85).aspx

Windows 2000: Do not create a named synchronization object in DllMain because the system will then load an additional DLL.

Русский военный корабль идёт ко дну!
Re[18]: Race condition
От: alsemm Россия  
Дата: 17.01.09 13:30
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


AG>>>Интересно.

AG>>>Для моего случая видимо стоит делать мьютекс так, чтобы он не был один на все копии процесса:
A>>Ну не знаю, Globals::globals_-то у вас одна на все процессы будет, если она в dll. А если в exe, то да, нужно имя мьютекса уникальное для каждого процесса заводить.

AG>не уловил, какая разница dll/exe, если синглтон один штук на одно адресное пространство в обоих случаях.

Разница такая, что если Globals::globals_ определена в dll, то она будет одинаковая во всех процессах, которые эту dll используют. Следовательно имя мьютекса должно быть одинаковым для все процессов из которых Globals::getInstance() может быть вызвано.
Если Globals::globals_ определена в exe, то на каждый экземпляр запущенного приложения будет своя копия Globals::globals_. Следовательно нужно имя мьютекса выбирать для каждого процесса свое, чтобы оно не конфликтовало с "соседями".

AG>http://msdn.microsoft.com/en-us/library/ms682583(VS.85).aspx

AG>

Windows 2000: Do not create a named synchronization object in DllMain because the system will then load an additional DLL.

Досадно. Возможно решить проблему можно статически слинковав свою dll с той DLL, которую дополнительно грузит система. Надо только узнать, чего она там грузит.

Алексей
Re[19]: Race condition
От: Alexander G Украина  
Дата: 17.01.09 13:52
Оценка:
Здравствуйте, alsemm, Вы писали:

A>Разница такая, что если Globals::globals_ определена в dll, то она будет одинаковая во всех процессах, которые эту dll используют.


С чего бы это ? она одна на каждый процесс.
Разве что если она в Shared section — так зачем её туда пихать?

A>Досадно. Возможно решить проблему можно статически слинковав свою dll с той DLL, которую дополнительно грузит система. Надо только узнать, чего она там грузит.


Нет, это не решит проблему.

Советую сходить по моей ссылке, найти процитированный текст, и почитать что там ещё рядом написано, ещё, если таки исользуешь инициализацию в dll, неплохо ознакомиться с документом здесь http://go.microsoft.com/FWLink/?LinkId=84138 .

Кстати, несколько месяцев назад вышла книга Рихтера "Windows via C/C++", новая версия этой
Автор(ы): Джеффри Рихтер
Это издание — практически новая книга, посвещенная программированию серьезных приложений на Microsoft Visual C++ в операционных системах Windows 2000 (32- и 64-разрядных версиях) и Windows 98 с использованием функций Windows API. Состоит из 27 глав, двух приложений и предметного указателя. Гораздо глубже, чем в предыдущих изданиях рассматриваются такие темы, как взаимодействие с операционной системой библиотеки C/C++, программирование DLL и оптимизация кода, описываются новые механизмы и функции, появившиеся в Windows 2000, и приводится информация, специфическая для 64-разрядной Windows 2000. В этом издании автор, перейдя с языка C на C++, переработал все программы-примеры и представил ряд новых приложений, например ProcessInfo и LISWatch. Также появились совершенно новые материалы: выравнивание данных, привязка потоков к процессорам, кэш-линии процессоров, архитектура NUMA, перехват API-вызовов и др. Книга предназначена профессиональным программистам, владеющим языком C/C++ и имеющим опыт разработки Windows-приложений. Прилагаемый компакт-диск содержит все программы из книги (исходный код и исполняемые файлы для процессоров x86, IA-64 и Alpha).
 книги.
Русский военный корабль идёт ко дну!
Re[2]: Lakos Singleton (nifty counting trick).
От: Alexander G Украина  
Дата: 17.01.09 14:36
Оценка:
Здравствуйте, remark, Вы писали:

R>Есть ещё вариант с т.н. Lakos Singleton (nifty counting trick). Он инициализируется при инициализации глобальных объектов, однако старается это делать до любого его использования:


//data.h
struct data_t
{
    void * p1;
    void * p2;
    int  i3;
    //...
};

template<typename>
struct data_initializer
{
    static unsigned counter;
    static data_t data;

    data_initializer()
    {
        if (++counter == 1)
        {
            data.p1 = ::GetModuleHandle(0);
            data.p2 = ::GetProcAddress(0, 0);
            data.i3 = (int)(42*_WIN32_WINNT);
        }
    }

    ~data_initializer()
    {
        if (--counter == 0)
        {
            // deinitialize data - nobody uses it now
        }
    }
};

template<typename T>
unsigned data_initializer<T>::counter = 0;

template<typename T>
data_t data_initializer<T>::data = {};

static data_initializer<void> data_initializer_; // internal linkage


R>В принципе, его можно обмануть при желании, поэтому желательно этот заголовочный файл включить во все файлы проекта, stdafx.h подойдёт. Тогда объект будет создан во время инициализации глобальных объектов, однако гарантированно до любого его использования. Плюс не вносит никаких издержек во время выполнения, т.е. никаких дополнительных проверок и индирекций при доступе к объекту.


Чем это лучше/хуже синглтона Мейерса, форсированного через конструктор глобального объекта с внутренним связыванием ?

R>

Русский военный корабль идёт ко дну!
Re[20]: Race condition
От: alsemm Россия  
Дата: 17.01.09 14:50
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


A>>Разница такая, что если Globals::globals_ определена в dll, то она будет одинаковая во всех процессах, которые эту dll используют.


AG>С чего бы это ? она одна на каждый процесс.

AG>Разве что если она в Shared section — так зачем её туда пихать?
Согласен, я ошибся.

A>>Досадно. Возможно решить проблему можно статически слинковав свою dll с той DLL, которую дополнительно грузит система. Надо только узнать, чего она там грузит.


AG>Нет, это не решит проблему.

Почему?

AG>Советую сходить по моей ссылке, найти процитированный текст, и почитать что там ещё рядом написано

Да вроде почитал.

С другой стороны, почему бы просто явно не инициализировать глобальные данные в DllMain?

Алексей
Re[21]: Race condition
От: Alexander G Украина  
Дата: 17.01.09 15:19
Оценка:
Здравствуйте, alsemm, Вы писали:

A>Да вроде почитал.


Тогда д.б. понятно, что ничего из дополнительных dll оттуда вызвать не получится.

A>С другой стороны, почему бы просто явно не инициализировать глобальные данные в DllMain?


Да. Это действительно правильное решение для dll. Крит.секции, анонимные мьютексы и события, слоты TLS можно проинициализировать из DllMain. Или, то же самое, но лучше — через конструкторы глобальных объектов.

У меня не dll. В ехе допускается "безответственный" подход, при котором многое происходит до main и неизвестно что именно. Поэтому в ехе и нужны хитрые инициализации.
Русский военный корабль идёт ко дну!
Re[17]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 18.01.09 11:51
Оценка:
Здравствуйте, alsemm, Вы писали:

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


A>>>В MS Windows никаких констант нет, значит инициализация g_guard будет не на этапе загрузки секций кода из exe в память, а позже, когда возможно уже какие-то глобальные функции вызываются.


R>>В Windows можно использовать boost или pthread-win32.

A>Там полностью свои сампописные муьютексы/крит. секции?

Да.

R>>А нужно именно Win32 API, то InitOnce. Если до Vista, то ничего хорошего для этой цели нет — либо самому морочиться с Interlocked операциями,

A>А чего с ними мучаться — взять просто boost::detail::spinlock и все дела.

Ну boost::detail::spinlock наврядли можно назвать Win32 API.


R>>либо premature pessimization в виде именованных объектов ядра для межпроцессного взаимодействия.

A>Ну getInstance() — это не тот случай, тобы такты экономить, не часто она и вызываться-то будет.

Откуда такая уверенность? Alexander G не хочет volatile и Interlocked добавлять, а тут уже 4 системных вызова получается. Я вполне могу представить себе ситуации, когда обращение к синглтону вынужденно получается очень частым...


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[17]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 18.01.09 12:04
Оценка:
Здравствуйте, alsemm, Вы писали:

R>>Плюс new (std::nothrow) не работает так, как ты думаешь, соотв. будем получать полное зависание процесса при исключении из new (std::nothrow).

A>Замечание по существу, но ыглядит как придирка, т.к. ничего не мешает или 'new Globals();' в try/catch обернуть, или просто принять, что конструктор Globals не должен кидать исключений.

Меня просто смутило (std::nothrow). Обычно, если пишут псевдо-код, то — просто new.


R>>Плюс CloseHandle() надо делать на каждый CreateMutex(), а не один раз. Один CloseHandle() ничего не закроет, т.к. хэндлов насоздавалась уже целая туча.

A>Да нет проблем:

Это уже 4 системных вызова получается... Нет, как теоретическо-абстрактное упражнение, это конечно интересно, но с практической т.з...



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[17]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 18.01.09 12:10
Оценка:
Здравствуйте, bkat, Вы писали:

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


AG>>Если бы всё было так просто, я бы действительно взял КС.


B>Все время забываю, что тебе нужна гарантия в случае если

B>кто-то пожелает вызвать getInstance при создании глобальных объектов.

B>Тут извини. Я пас...

B>Чем-то придется пожертвовать.

Жертвовать не обязательно — достаточно мьютекс с возможностью статической инициализации (boost, pthread).


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[18]: Race condition
От: alsemm Россия  
Дата: 18.01.09 12:23
Оценка:
Здравствуйте, remark, Вы писали:

R>Ну boost::detail::spinlock наврядли можно назвать Win32 API.

Я этого и не утверждал.

Посмотрел на boost-овые мьютексы. boost::mutex нельзя использовать для защиты доступа к глобальным данным: у него дефолтовый конструктор определен. А вот boost::detail::basic_timed_mutex вполне подойдет.

R>Откуда такая уверенность? Alexander G не хочет volatile и Interlocked добавлять, а тут уже 4 системных вызова получается. Я вполне могу представить себе ситуации, когда обращение к синглтону вынужденно получается очень частым...

Например?

Алексей
Re[22]: Race condition
От: alsemm Россия  
Дата: 18.01.09 12:28
Оценка:
AG>Тогда д.б. понятно, что ничего из дополнительных dll оттуда вызвать не получится.
Я ж написал про статическую линковку, а не про LoadLibrary.

AG>У меня не dll. В ехе допускается "безответственный" подход, при котором многое происходит до main и неизвестно что именно. Поэтому в ехе и нужны хитрые инициализации.

Тогда или именованный мьютекс, или boost::detail::basic_timed_mutex вполне подойдут.

Алексей
Re[18]: Race condition
От: Alexander G Украина  
Дата: 18.01.09 14:20
Оценка:
Здравствуйте, remark, Вы писали:

R>>>либо premature pessimization в виде именованных объектов ядра для межпроцессного взаимодействия.

A>>Ну getInstance() — это не тот случай, тобы такты экономить, не часто она и вызываться-то будет.

R>Откуда такая уверенность? Alexander G не хочет volatile и Interlocked добавлять, а тут уже 4 системных вызова получается. Я вполне могу представить себе ситуации, когда обращение к синглтону вынужденно получается очень частым...


Именно.

Мой синглтон, как я уже писал, — набор жучков для отладки, такой чтобы в креш-дамп писалась нужная информация. На данный момент это, допустим, получение некоторой системной информации, плюс регистрация блоков памяти некоторых объектов. В будущем, по мере анализа крэшдампов новых версий, туда будет что-то добавляться, что-то убираться, и не исключён вызов getInstance() из более узких мест, чем можно предположить сейчас. "Затащить" в глубину цикла ссылку на синглтон плюс убрать потом, когда больше та информация не нужна — много лишней возни. Особенно в случае регистрации блоков памяти, когда синглтон нужен из своих операторов new/delete — это ж нужно настраивать сматрпоинтеры, и из них вызвать явно деструктор плюс delete-с-контекстом.
В общем, мне нужен синглтон, желательно без блокировок, и без преждевременной пессимизации — что бы не говорили теоретики
Русский военный корабль идёт ко дну!
Re[4]: Race condition
От: Аноним  
Дата: 18.01.09 14:21
Оценка:
AG>>Ещё, можно ли убрать volatile и заменить его на _WriteBarrier(); перед сохранением указателя ?

R>Нет.

R>Ну точнее так — на x86 должно работать. А на IA-64 обязателен аппаратный барьер.

а разве _WriteBarrier не является и аппаратным барьером тоже?

т.е. как может перегруппироваться на процессоре IA-64 код:
data_t* init()
{
    data_t* p = p_data;
    if (p)
        return p;
    data.p1 = ::GetModuleHandle(...);
    data.p2 = ::GetProcAddress(...);
    data.i3 = (int)sqrt(42*_WIN_VER);
   _WriteBarrier();
    p_data = &data;
    return p_data;
}

, чтобы начать неправильно работать?
Re[23]: Race condition
От: Alexander G Украина  
Дата: 18.01.09 14:27
Оценка:
Здравствуйте, alsemm, Вы писали:

AG>>Тогда д.б. понятно, что ничего из дополнительных dll оттуда вызвать не получится.

A>Я ж написал про статическую линковку, а не про LoadLibrary.

Статическая линковка — это, я понимаю, добавление соотв функций в таблицу импорта ?
Если так, то не поможет. Возможно сменит проявление с дедлока на AV или ещё что-то — но не более того. Там по ссылкам всё написано.
Русский военный корабль идёт ко дну!
Re[19]: Race condition
От: alsemm Россия  
Дата: 18.01.09 14:32
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Мой синглтон, как я уже писал, — набор жучков для отладки, такой чтобы в креш-дамп писалась нужная информация. На данный момент это, допустим, получение некоторой системной информации, плюс регистрация блоков памяти некоторых объектов. В будущем, по мере анализа крэшдампов новых версий, туда будет что-то добавляться, что-то убираться, и не исключён вызов getInstance() из более узких мест, чем можно предположить сейчас. "Затащить" в глубину цикла ссылку на синглтон плюс убрать потом, когда больше та информация не нужна — много лишней возни. Особенно в случае регистрации блоков памяти, когда синглтон нужен из своих операторов new/delete — это ж нужно настраивать сматрпоинтеры, и из них вызвать явно деструктор плюс delete-с-контекстом.

AG>В общем, мне нужен синглтон, желательно без блокировок, и без преждевременной пессимизации — что бы не говорили теоретики
Тогда не удобнее-ли оформить своих жучков в виде отдельной dll? Инициализацию глобальных данных делать в DllMain без всяких синхронизаций лишних.

Алексей
Re[20]: Race condition
От: Alexander G Украина  
Дата: 18.01.09 14:45
Оценка:
Здравствуйте, alsemm, Вы писали:

A>Тогда не удобнее-ли оформить своих жучков в виде отдельной dll? Инициализацию глобальных данных делать в DllMain без всяких синхронизаций лишних.


Нет. dll принесёт свои неудобства.

На самом деле сейчас жучки вполне работоспособны с инициализацией в конструкторе глобальной переменной. Но хотелось бы увеличить уровень защищённости от дурня (меня же ).
Русский военный корабль идёт ко дну!
Re[24]: Race condition
От: alsemm Россия  
Дата: 18.01.09 14:46
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


AG>>>Тогда д.б. понятно, что ничего из дополнительных dll оттуда вызвать не получится.

A>>Я ж написал про статическую линковку, а не про LoadLibrary.

AG>Статическая линковка — это, я понимаю, добавление соотв функций в таблицу импорта ?

AG>Если так, то не поможет. Возможно сменит проявление с дедлока на AV или ещё что-то — но не более того. Там по ссылкам всё написано.
Статическая линковка с библиотекой A.dll приведет к тому что, LoadLibrary(A.dll) будет сделано до вызова вашей DllMain. Соответ. когда надо будет создавать именованный мьютекс и неявно будет сделан LoadLibrary(A.dll), то он ничего по факту делать не будет (счетчик ссылок на A.dll увеличит просто).
http://msdn.microsoft.com/en-us/library/ms682583.aspx:

Because Kernel32.dll is guaranteed to be loaded in the process address space when the entry-point function is called, calling functions in Kernel32.dll does not result in the DLL being used before its initialization code has been executed.

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

Алексей
Re[21]: Race condition
От: alsemm Россия  
Дата: 18.01.09 14:48
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Нет. dll принесёт свои неудобства.

Дело ваше конечно, но в чем неудобства?

Алексей
Re[22]: Race condition
От: Alexander G Украина  
Дата: 18.01.09 16:29
Оценка:
Здравствуйте, alsemm, Вы писали:

A>Дело ваше конечно, но в чем неудобства?


Ну что проще — вставить инициализацию отсюда
Автор: remark
Дата: 16.01.09
вместо существующей, или:
— придумать бинарный интерфейс между dll и exe
— добавить файл в build и deployment
Русский военный корабль идёт ко дну!
Re[4]: Race condition
От: Alexander G Украина  
Дата: 18.01.09 16:32
Оценка:
Здравствуйте, remark, Вы писали:

R>Идея такая, что если какой-то глобальный объект использует наш синглтон, то data.h должен быть подключен в этот файл, и data_initializer_ будет определен в этом файле раньше того глобального объекта.


Раньше определён — раньше создан ? Почему ?
Русский военный корабль идёт ко дну!
Re[4]: Race condition
От: Alexander G Украина  
Дата: 18.01.09 17:07
Оценка:
Здравствуйте, remark, Вы писали:

R>"Для порядка". Должен был быть и барьер компилятора и аппаратный. Аппаратный убрали, компилятора должен остаться. Хотя, возможно, из-за зависимости по-управлению, он тут и не обязателен, но это такая область, где лучше переборщить, чем недоборщить.


Может у меня не хватает фантазии, но я не редставляю, как компилятор может испортить. Мне кажется, что такое
    data_t* p = p_data;
    _ReadWriteBarrier();
    if (p)
        return p;
    data.p1 = ::GetModuleHandle(...);

можно заменить на такое
    if (p_data)
        return p_data;
    _ReadWriteBarrier();
    data.p1 = ::GetModuleHandle(...);

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

AG>>Ещё, можно ли убрать volatile и заменить его на _WriteBarrier(); перед сохранением указателя ?


R>Нет.

R>Ну точнее так — на x86 должно работать. А на IA-64 обязателен аппаратный барьер.

http://msdn.microsoft.com/en-us/library/f20w0x5e(VS.80).aspx

Marking memory with a memory barrier is similar to marking memory with the volatile (C++) keyword.

?
Русский военный корабль идёт ко дну!
Re[5]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 19.01.09 09:50
Оценка:
Здравствуйте, Аноним, Вы писали:

AG>>>Ещё, можно ли убрать volatile и заменить его на _WriteBarrier(); перед сохранением указателя ?


R>>Нет.

R>>Ну точнее так — на x86 должно работать. А на IA-64 обязателен аппаратный барьер.

А>а разве _WriteBarrier не является и аппаратным барьером тоже?


Документация оставляет желать лучшего. Но по факту на x86 _ReadWriteBarrier() не выливается в mfence. Хотя с другой стороны в документации написано, что _ReadWriteBarrier() это "как volatile"...


А>т.е. как может перегруппироваться на процессоре IA-64 код:

, чтобы начать неправильно работать?

Я имел в виду вот такой вариант (соотв. исходя из того, что _WriteBarrier — это НЕ аппаратный барьер):

data_t* init()
{
    data_t* p = p_data;
    if (p)
        return p;
    data.p1 = ::GetModuleHandle(...);
    data.p2 = ::GetProcAddress(...);
   _WriteBarrier();
    p_data = &data;
    data.i3 = (int)sqrt(42*_WIN_VER);
    return p_data;
}



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[6]: Race condition
От: Alexander G Украина  
Дата: 19.01.09 13:10
Оценка:
Здравствуйте, remark, Вы писали:

R>У них, честно говоря, такая дерьмовая документация, что уже отчаялся в ней что-либо понять. Нет, что б явно и чётко всё написать, всё какими-то намёками выражаются...

R>По моим представлениям, если бы эти _ReadWriteBarrier/_WriteBarrier/_ReadBarrier были бы и аппаратными барьерами, то на x86 _ReadWriteBarrier должен был бы выливаться в инструкцию mfence. Этого не происходит. Поэтому я считаю, что это — только барьеры компилятора... Хотя интересно было бы поглядеть как _WriteBarrier компилируется на IA-64 — я не знаю...

x86


  int i = 1;
004131AD  mov         dword ptr [ebp-228h],1 
  _ReadWriteBarrier();
  ++i;
004131B7  mov         eax,dword ptr [ebp-228h] 
004131BD  add         eax,1 
004131C0  mov         dword ptr [ebp-228h],eax 
  _ReadWriteBarrier();


  volatile int j = 1;
004131C6  mov         dword ptr [ebp-234h],1 
  ++j;
004131D0  mov         eax,dword ptr [ebp-234h] 
004131D6  add         eax,1 
004131D9  mov         dword ptr [ebp-234h],eax




x64


  int i = 1;
000000014000214B  mov         dword ptr [rsp+248h],1 
  _ReadWriteBarrier();
  ++i;
0000000140002156  mov         eax,dword ptr [rsp+248h] 
000000014000215D  add         eax,1 
0000000140002160  mov         dword ptr [rsp+248h],eax 
  _ReadWriteBarrier();


  volatile int j = 1;
0000000140002167  mov         dword ptr [rsp+24Ch],1 
  ++j;
0000000140002172  mov         eax,dword ptr [rsp+24Ch] 
0000000140002179  add         eax,1 
000000014000217C  mov         dword ptr [rsp+24Ch],eax


никакой mfence на х86 и х64 не нужно ?
Русский военный корабль идёт ко дну!
Re[6]: Race condition
От: Аноним  
Дата: 19.01.09 13:27
Оценка:
Здравствуйте, remark, Вы писали:
...

А какой практический смысл есть в чисто "НЕ аппаратных" барьерах, если, полюбому, на процессоре всё может перемешаться, как ему угодно?
Re[7]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 19.01.09 20:14
Оценка:
Здравствуйте, Аноним, Вы писали:

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

А>...

А>А какой практический смысл есть в чисто "НЕ аппаратных" барьерах, если, полюбому, на процессоре всё может перемешаться, как ему угодно?


Есть.
С их помощью можно (1) упорядочивать между потоком и обработчиком сигнала для этого же потока, (2) упорядочивать между 2 потоками, которые выполняются на одном процессоре, (3) использовать, если упорядочивание на аппаратном уровне выполняется другими средствами (обычно в таком случае барьер компилятора стоит при всех обращениях, т.к. он бесплатный; а цена аппаратного барьера амортизируется на множество обращений; хороший пример RCU в ядре Linux).
Так же стоит отметить, что барьеры компилятора включены в C++0x — там смотри std::atomic_signal_fence(std::memory_order).


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[7]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 19.01.09 20:16
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


R>>У них, честно говоря, такая дерьмовая документация, что уже отчаялся в ней что-либо понять. Нет, что б явно и чётко всё написать, всё какими-то намёками выражаются...

R>>По моим представлениям, если бы эти _ReadWriteBarrier/_WriteBarrier/_ReadBarrier были бы и аппаратными барьерами, то на x86 _ReadWriteBarrier должен был бы выливаться в инструкцию mfence. Этого не происходит. Поэтому я считаю, что это — только барьеры компилятора... Хотя интересно было бы поглядеть как _WriteBarrier компилируется на IA-64 — я не знаю...

AG>x86

AG>x64
AG>никакой mfence на х86 и х64 не нужно ?

На IA-32 и Intel 64 (x86-32/64) барьеры не нужны. А на IA-64 (Itanium) при volatile обращениях ты должен увидеть инструкции ld.acq/st.rel (load-acquire/store-release) вместо обычных ld/st (load/store).


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[8]: Race condition
От: Alexander G Украина  
Дата: 19.01.09 21:14
Оценка:
Здравствуйте, remark, Вы писали:

R>На IA-32 и Intel 64 (x86-32/64) барьеры не нужны. А на IA-64 (Itanium) при volatile обращениях ты должен увидеть инструкции ld.acq/st.rel (load-acquire/store-release) вместо обычных ld/st (load/store).


Ну т.е. нет никаких свидетельств что барьеры "хуже" volatile ?

(ассемблера IA64 не знаю, для IA64 не собираю)

R>
Русский военный корабль идёт ко дну!
Re[9]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 20.01.09 07:02
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


R>>На IA-32 и Intel 64 (x86-32/64) барьеры не нужны. А на IA-64 (Itanium) при volatile обращениях ты должен увидеть инструкции ld.acq/st.rel (load-acquire/store-release) вместо обычных ld/st (load/store).


AG>Ну т.е. нет никаких свидетельств что барьеры "хуже" volatile ?


В смысле хуже?

1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[10]: Race condition
От: Alexander G Украина  
Дата: 20.01.09 07:58
Оценка:
Здравствуйте, remark, Вы писали:

R>В смысле хуже?


Я имею ввиду, что можно заменить voiatile на барьеры, как пишут в MSDN, т.к. машииный код такой же
Русский военный корабль идёт ко дну!
Re[11]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 20.01.09 15:29
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


R>>В смысле хуже?


AG>Я имею ввиду, что можно заменить voiatile на барьеры, как пишут в MSDN, т.к. машииный код такой же


Мммм... ну у меня всё ещё остаётся подозрение, что volatile в MSVC — это и барьер компилятора и аппаратный, в барьеры — это только компилятора. Т.к. иначе не понятно почему _ReadWriteBarrier() не эмитит mfence на x86.
Плюс они, так сказать, ортогональны друг другу. В том смысле, что например при захвате мьютекса нужен барьер типа load-acquire (или #LoadLoad|#LoadStore membar в терминологии SPARC), и volatile load даёт именно это; точно же смоделировать это с помощью барьеров не получается, т.к. _ReadBarrier() и _WriteBarrier() каждый по отдельности недостаточны, а _ReadWriteBarrier() слишком тяжелый (хммм... точнее он бы слишком тяжелым, если бы эмитил mfence, а так вообще не понятно, что это такое). Честно говоря, я чем больше читаю их документацию, тем больше запутываюсь


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[12]: Race condition
От: Alexander G Украина  
Дата: 20.01.09 21:54
Оценка:
Здравствуйте, remark, Вы писали:

R>Т.к. иначе не понятно почему _ReadWriteBarrier() не эмитит mfence на x86.


Зачем там mfence ?

Кстати, почему-то __mf intrinsic только для Itanium. Видимо, кроме инлайн ассемблера никак этот самый _mfence на х86 не сделать. нужен ли он ?
Русский военный корабль идёт ко дну!
Re[13]: Race condition
От: remark Россия http://www.1024cores.net/
Дата: 21.01.09 11:19
Оценка:
Здравствуйте, Alexander G, Вы писали:

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


R>>Т.к. иначе не понятно почему _ReadWriteBarrier() не эмитит mfence на x86.


AG>Зачем там mfence ?


Полный барьер на x86 должен отображаться на mfence.


AG>Кстати, почему-то __mf intrinsic только для Itanium. Видимо, кроме инлайн ассемблера никак этот самый _mfence на х86 не сделать. нужен ли он ?


_mm_mfence() в заголовке <intrin.h>. Да, нужен для многих алгоритмов синхронизации.



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[11]: Race condition
От: Quasi  
Дата: 30.01.09 10:18
Оценка:
Здравствуйте, remark, Вы писали:

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


Q>>Для процессоров IA-32 и Intel 64 (Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Intel Xeon, P6 family, Pentium 4 и более ранних),

Q>>AMD64 и более ранних операции чтения из памяти и запись всегда имеют семантику Acquire и Release соответственно (если не считать "string operations" и "streaming stores"). Однако это не мешает и вышеописанные процессоры (кроме совсем ранних) переупорядочивают чтение и предшествующую запись, если они не относятся к одному адресу памяти.

R>АФАИК, они переупорядочивают даже обращения к одному адресу памяти. Свои обращения процессор-то, конечно, увидит в программном порядке, а вот другие процессоры могут увидеть обращения к одному адресу памяти в отличном от программного.

В общем, с этой точки зрения да, такое поведение документированно.


R>А почему это вводит в заблуждение? Ведь volatile в данном случае тоже не приведёт к добавлению mfence/lock.

MSDN, volatile:
This allows volatile objects to be used for memory locks and releases in multithreaded applications

В то время как, _ReadWriteBarrier (

is similar to marking memory with the volatile (C++) keyword

) никакого отношения к упорядочиванию операций в памяти процессором не имеет, по факту.


R>

Re[12]: Race condition
От: Alexander G Украина  
Дата: 02.02.09 13:14
Оценка:
Здравствуйте, Quasi, Вы писали:

R>>А почему это вводит в заблуждение? Ведь volatile в данном случае тоже не приведёт к добавлению mfence/lock.

Q>

Q>MSDN, volatile:
Q>This allows volatile objects to be used for memory locks and releases in multithreaded applications

Q>В то время как, _ReadWriteBarrier (

is similar to marking memory with the volatile (C++) keyword

) никакого отношения к упорядочиванию операций в памяти процессором не имеет, по факту.


Т.е. надо вставлять _mm_fence вручную ?

struct data_t
{
  void * p1;
  void * p2;
  int  i3;
  ...
} data = {0};

data_t* p_data = 0;

data_t* init()
{
    if (p_data)
        return p_data;
    _ReadWriteBarrier();
    data.p1 = ::GetModuleHandle(...);
    data.p2 = ::GetProcAddress(...);
    data.i3 = (int)sqrt(42*_WIN_VER);
    _mm_mfence();
    p_data = &data;
    return p_data;
}
Русский военный корабль идёт ко дну!
Re[14]: Race condition
От: Alexander G Украина  
Дата: 02.02.09 17:18
Оценка:
Здравствуйте, Quasi, Вы писали:

Q>Здравствуйте, Alexander G, Вы писали:


AG>>Т.е. надо вставлять _mm_fence вручную ?


AG>>
AG>>struct data_t
AG>>{
AG>>  void * p1;
AG>>  void * p2;
AG>>  int  i3;
AG>>  ...
AG>>} data = {0};

AG>>data_t* p_data = 0;

AG>>data_t* init()
AG>>{
AG>>    if (p_data)
AG>>        return p_data;
AG>>    _ReadWriteBarrier();
AG>>    data.p1 = ::GetModuleHandle(...);
AG>>    data.p2 = ::GetProcAddress(...);
AG>>    data.i3 = (int)sqrt(42*_WIN_VER);
AG>>    _mm_mfence();
AG>>    p_data = &data;
AG>>    return p_data;
AG>>}
AG>>


Q> В данном случае для предотвращения переупорядочивания достаточно записи указателя p_data c release семантикой (на x86, Intel 64 и AMD64 для этого ничего дополнительно не требуется).


Я не зочу volatile т.к. его смысл зависит от компилятора.
Может тогда так ?
data_t* init()
{
    if (p_data)
       return p_data;
    _ReadWriteBarrier();
    data.p1 = ::GetModuleHandle(...);
    data.p2 = ::GetProcAddress(...);
    data.i3 = (int)sqrt(42*_WIN_VER);
#if defined(_M_IX86) || defined(_M_IA64) || defined(_M_X64)
    // soft write barrier is enough
    _WriteBarrier();
    p_data = &data;
#else
    #error add a proper barrier for this architecture
#endif
    return p_data;
}
Русский военный корабль идёт ко дну!
Re[15]: Race condition
От: Quasi  
Дата: 02.02.09 17:57
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Я не зочу volatile т.к. его смысл зависит от компилятора.

AG>Может тогда так ?
AG>
AG>data_t* init()
AG>{
AG>    if (p_data)
AG>       return p_data;
AG>    _ReadWriteBarrier();
AG>    data.p1 = ::GetModuleHandle(...);
AG>    data.p2 = ::GetProcAddress(...);
AG>    data.i3 = (int)sqrt(42*_WIN_VER);
AG>#if defined(_M_IX86) || defined(_M_IA64) || defined(_M_X64)
AG>    // soft write barrier is enough
AG>    _WriteBarrier();
AG>    p_data = &data;
AG>#else
AG>    #error add a proper barrier for this architecture
AG>#endif
AG>    return p_data;
AG>}
AG>


Как раз для семейства Intel Itanium (_M_IA64) барьера компилятора не достаточно, под Windows проще всего:
    //.....
    ::InterlockedExchangePointer(reinterpret_cast<void**>(&p_data), &data);
Re[16]: Race condition
От: Quasi  
Дата: 02.02.09 18:04
Оценка:
Здравствуйте, Quasi, Вы писали:

Q>Здравствуйте, Alexander G, Вы писали:



Q>Как раз для семейства Intel Itanium (_M_IA64) барьера компилятора не достаточно, под Windows проще всего:

Q>
Q>    //.....
Q>    ::InterlockedExchangePointer(reinterpret_cast<void**>(&p_data), &data);
Q>


MSDN, InterlockedExchangePointer:

This function generates a full memory barrier (or fence) to ensure that memory operations are completed in order.

Re[16]: Race condition
От: Alexander G Украина  
Дата: 02.02.09 18:57
Оценка:
Здравствуйте, Quasi, Вы писали:

Q>Как раз для семейства Intel Itanium (_M_IA64) барьера компилятора не достаточно, под Windows проще всего:

Q>
Q>    //.....
Q>    ::InterlockedExchangePointer(reinterpret_cast<void**>(&p_data), &data);
Q>


Так можно ?

struct data_t
{
  void * p1;
  void * p2;
  int  i3;
} data = {0};

union
{
  data_t* d;
  void volatile* p;
} p_data;

 
data_t* init()
{
  if (p_data.d)
    return p_data.d;
  if (void * d = InterlockedCompareExchangePointer(&p_data.p, 0, 0))
    return reinterpret_cast<data_t*>(d);
  data.p1 = ::GetModuleHandle(...);
  data.p2 = ::GetProcAddress(...);
  data.i3 = (int)sqrt(42*_WIN_VER);
  return reinterpret_cast<data_t*>(
    InterlockedExchangePointer(&p_data.p, &data));
}
Русский военный корабль идёт ко дну!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.