Как инициализировать статическую переменную в многопоточном контексте? - PullRequest
3 голосов
/ 22 января 2011

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

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, так и с потоками Windows.

РЕДАКТИРОВАТЬ: причина, по которой я хочу эту функциональность, заключается в том, что она предоставляет ненавязчивый способ проверить, есть ли разница при запуске фрагмента кода однопоточный или многопоточный (предназначен для отладки).*

Ответы [ 4 ]

3 голосов
/ 22 января 2011

Один из подходов заключается в использовании глобальной блокировки для сериализации путей инициализации.Однако вам понадобится переносная оболочка поверх барьера памяти 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
    // ....
}

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

3 голосов
/ 22 января 2011

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

int deepthought()
{
    return 42;
}

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

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

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

2 голосов
/ 22 января 2011

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

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

1 голос
/ 22 января 2011

Существуют различные варианты:

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

    static pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
    
...