2011-01-22 6 views
3

я придумал хорошее использование статического ключевого слова внутри функции, чтобы быть что-то вроде этого:Как инициализировать статическую переменную в многопоточном контексте?

void threadSafeWrite(int *array, int writeIndex, int writeData){ 
    static void *threadLock = Lock_create(); //in my code locks are void* to be cross-platform compatable 
    Lock_aquire(threadLock); 
    array[writeIndex] = writeData; 
    Lock_release(threadLock); 
} 

Короче говоря, кажется, как хороший способ, чтобы сделать критическую секцию. Мой вопрос заключается в том, как инициализировать threadLock в потоковом режиме? Проблема с примером, я боюсь, заключается в том, что блокировка будет распределяться несколько раз, и каждый поток будет использовать другую блокировку. Есть какие нибудь идеи как это починить? Похоже на проблему с курицей и яйцом. Я хочу решение (или решения), которое работает как с потоками pthreads, так и с окнами.

EDIT: причина, по которой я хочу эту функциональность, заключается в том, что она обеспечивает неинтрузивный способ проверить, есть ли разница при выполнении части кода однопоточной или многопоточной (предназначенной для целей отладки).

ответ

3

Один из подходов - использовать глобальную блокировку для сериализации путей инициализации. Однако вам понадобится переносная оболочка поверх SMP-памяти; барьер приобретения, подразумеваемый блокировкой, равен , а не, поскольку он, в принципе, позволяет компилятору и/или процессору кэшировать результаты чтения памяти с момента обнаружения блокировки. Вот пример:

Lock global_init_lock; // Should have low contention, as it's only used during startup 

void somefunc() { 
    static void *data; 
    static long init_flag = 0; 
    if (!init_flag) { // fast non-atomic compare for the fast path 
     global_init_lock.Lock(); 
     read_memory_barrier(); // make sure we re-read init_flag 
     if (!init_flag) 
      data = init_data(); 
     write_memory_barrier(); // make sure data gets committed and is visible to other procs 
     init_flag = 1; 
     global_init_lock.Unlock(); 
    } 
    read_memory_barrier(); // we've seen init_flag = 1, now make sure data is visible 
    // .... 
} 

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

+0

Read_memory_barrier не требуется, поскольку это подразумевается блокировкой. Однако это двукратно проверенная идиома блокировки, которая недопустима в большинстве моделей памяти. Второй поток может видеть 'init_flag = 1', но устаревшее значение' data' или вещи, на которые он указывает. – jilles

+0

@jilles Я не знаю о «большинстве моделей памяти». –

+0

Вы можете сделать это с помощью CAS и избежать блокировки, если вы не возражаете, чтобы несколько раз называть init_data и быть готовым бросить кого-то в этом случае. –

3

Не работает, поскольку инициализатор переменной static должен быть константой в C. Вызов функции не является константой. Это отличается от C++, где вы можете сделать работу static до ввода main. Например, это не будет компилироваться:

int deepthought() 
{ 
    return 42; 
} 

void ask() 
{ 
    static int answer = deepthought(); 
} 

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

P.S .: Я советую использовать void *, чтобы получить непрозрачный указатель. Вместо этого реализуйте одноэлементный одноэлементный код struct Lock для обеспечения безопасности типов.

+0

взгляд на bdonlans ответить хотя хороший совет по пустоте *, что им с помощью. – chacham15

2

Неполадка Общая идея функции Lock_create; для его создания потребуется синхронизация, которую вы не можете гарантировать, потому что у вас еще нет блокировки! Не используйте указатели для замков. Вместо этого создайте структуру и передайте адрес этой структуры вашим функциям блокировки и разблокировки. Или еще лучше, используйте те, которые предоставляются вашей ОС, так как нет возможности реализовать блокировку на обычном C.

Также все, кто сказал, что вам нужно сделать блокировку «глобальным», неверны. Область функционального уровня прекрасна до тех пор, пока у вас есть статическая продолжительность хранения. Вам просто нужно устранить функцию распределения Lock_create и использовать соответствующий тип для блокировки.

0

Есть различные варианты:

  • вручную вызвать код инициализации в безопасное время (например, прежде чем подвергать код для дополнительных потоков).
  • Используйте pthread_once() или его эквивалент, что гарантирует, что код инициализации вызывается один раз, и все последующие вызывающие его видят его эффекты.
  • Используйте статическую инициализацию блокировки, которая не требует вызова какой-либо функции. Например, при использовании Pthreads

    static pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER; 
    
Смежные вопросы