Модераторы: Daevaorn
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Последовательная консистенция. Параллельные вычисления.  
:(
    Опции темы
SABROG
  Дата 5.6.2010, 20:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Hacker
****


Профиль
Группа: Завсегдатай
Сообщений: 2481
Регистрация: 18.9.2006

Репутация: 4
Всего: 91



Прочитал статью. Всё обсуждение идет на примере Java, но основные вещи применимы к C++. Если кратко, то из этой статьи я вынес следующие 3 проблемы в параллельных вычислениях:
  •  Указатель на выделенный участок памяти под объект может быть записан в другую переменную до вызова самого конструктора. Как следствие другой поток может считать этот указатель и попытаться работать с не проинициализированным объектом.
  •  Чтобы не работать с общей переменной к которой есть доступ у других потоков мы копируем её значение во вторую - локальную переменную. Компилятор может оптимизировать код таким образом, что просто выкинет "посредника" и вся "защита" пойдет прахом.
  •  Процессор или компилятор могут инициализировать данные в иной последовательности чем в файле исходника программы (нарушается последовательная консистенция)

Собственно первая проблема решается через кеширование указателя на данные, чтобы присвоить его глобальной переменной после выполнения конструктора. Тут вытекает проблема номер 2 с возможной оптимизацией компилятора, которая решается через объявление переменной с ключевым словом volatile. Далее идут непонятки. Автор статьи пишет:

Цитата

    * The problem here is not with sequential consistency. Code is being moved, not reordered.
    * Many JVMs do not implement volatile correctly regarding sequential consistency anyway.


То есть проблема уже не в последовательной консистенции, а в том, что перемещается сам код. Куда, зачем и каким образом он не указал, поэтому тут остается пробел. Статья сама заканчивается на плохой ноте о несостоятельности паттерна Double-Checking Locking, и вместо него он предлагает Java программистам использовать static переменную.

Теперь возвращаясь к C++. Все знают паттерн Singleton, который в не тех руках быстро превращается в анти-паттерн. Большинство примеров в интернете не потокобезопасные и не реентерабельные. Есть вариант в функции getInstance() которого используется boost::call_once() или std::call_once() из нового стандарта C++0x. Сам исходный код этой функции находится тут. Выглядит он так:

Код

Header fast_pthread_once.h

The header defines the synchronization type, defines a synchronization sentinal values, declares the synchronization function, and defines an inline fast synchronization function.


#ifndef FAST_PTHREAD_ONCE_H
#define FAST_PTHREAD_ONCE_H

#include <signal.h>
#include <stdint.h>

typedef sig_atomic_t fast_pthread_once_t;
#define FAST_PTHREAD_ONCE_INIT SIG_ATOMIC_MAX
extern __thread fast_pthread_once_t _fast_pthread_once_per_thread_epoch;

#ifdef __cplusplus
extern "C" {
#endif

extern void fast_pthread_once( pthread_once_t *once, void (*func)(void) );

inline static void fast_pthread_once_inline(
    fast_pthread_once_t *once, void (*func)(void) )
{
    fast_pthread_once_t x = *once;        /* unprotected access */
    if ( x > _fast_pthread_once_per_thread_epoch ) {
        fast_pthread_once( once, func );
    }
}

#ifdef __cplusplus
}
#endif

#endif FAST_PTHREAD_ONCE_H

Source fast_pthread_once.c

The source is written in C. The lines of the primary function are numbered for reference in the subsequent correctness argument.


#include "fast_pthread_once.h"

#include <pthread.h>

static pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
  /* protects global_epoch and all fast_pthread_once_t writes */

static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
  /* signalled whenever a fast_pthread_once_t is finalized */

#define BEING_INITIALIZED (FAST_PTHREAD_ONCE_INIT - 1)

static fast_pthread_once_t global_epoch = 0; /* under mu */

__thread fast_pthread_once_t _fast_pthread_once_per_thread_epoch;

static void check( int x )
{
    if ( x == 0 )
        abort();
}

void fast_pthread_once( fast_pthread_once_t *once, void (*func)(void) )
{
/*01*/    fast_pthread_once_t x = *once;        /* unprotected access */
/*02*/    if ( x > _fast_pthread_once_per_thread_epoch ) {
/*03*/        check( pthread_mutex_lock(&mu) == 0 );
/*04*/        if ( *once == FAST_PTHREAD_ONCE_INIT ) {
/*05*/            *once = BEING_INITIALIZED;
/*06*/            check( pthread_mutex_unlock(&mu) == 0 );
/*07*/            (*func)();
/*08*/            check( pthread_mutex_lock(&mu) == 0 );
/*09*/            global_epoch++;
/*10*/            *once = global_epoch;
/*11*/            check( pthread_cond_broadcast(&cv) == 0 );
/*12*/        } else {
/*13*/            while ( *once == BEING_INITIALIZED ) {
/*14*/                check( pthread_cond_wait(&cv, &mu) == 0 );
/*15*/            }
/*16*/        }
/*17*/        _fast_pthread_once_per_thread_epoch = global_epoch;
/*18*/        check (pthread_mutex_unlock(&mu) == 0);
          }
}



Если внимательно посмотреть, то можно выделить характерные черты паттерна Double-Checking Locking. А также то, что тип fast_pthread_once_t является атомарным и volatile.

В принципе в этом коде нет первых двух проблем о которых я говорил в начале поста. Компилятор атомарные типы выкидывать не будет, динамически созданных объектов и передачи ссылок на него в глобальную переменную тут нет. А вот есть ли тут нарушение последовательной консистенции (Sequential Consistency)?

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

Код

/*05*/            *once = BEING_INITIALIZED;
/*09*/            global_epoch++;
/*10*/            *once = global_epoch;


Предположим первый поток пропускает 5 и 9 строку и сразу инициализирует *once = global_epoch. В этом случае, когда второй поток вытеснит первый он сравнит *once, в котором будет старое значение global_epoch (0 - на момент первой инициализации) и неопределенной значение находящееся в переменной _fast_pthread_once_per_thread_epoch, так как она никогда не инициализировалась (почему? У __thread переменных есть default initialized value?). Итак x == 0, а _fast_pthread_once_per_thread_epoch, предположим, тоже 0. Тогда ничего не произойдет. Если _fast_pthread_once_per_thread_epoch будет -1, тогда поток залочится на мутексе. Вроде бы ничего страшного.

Тогда предположим, что поток выполняет 9 строчку первой. В этом случае тоже ничего страшного не будет, так как global_epoch нигде не проверяется за пределами мутекса.

Собственно 3 вопроса.

- обеспечивают ли атомарные переменные/операции в C++ соблюдение последовательной консистенции?
- если не обеспечивают, то каким образом этого добиться?
- почему переменную global_epoch не сделали 3-state: BEING_INITIALIZED, FAST_PTHREAD_ONCE_INIT, INITIALIZED. Для чего её накручивать? В статье говорится, чтобы считать количество проинициализированных функций вызывающих once_call. Если флаги будут динамическими, а не статическими и будут вызываться масса различных функций, то счетчик рано или поздно пойдет на второй круг, а 2 вызова приходящиеся на значения BEING_INITIALIZED, FAST_PTHREAD_ONCE_INIT будут просто проигнорированы, то есть некоторые 2 функции не будут вызваны ниразу несмотря на то, что вызывался once_call.



--------------------
Национальная группа Russian Federation на QtCentre.
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0517 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.