одновременный доступ к переменной в c - PullRequest
5 голосов
/ 16 мая 2009

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

typedef struct {
  pthread_mutex_t mutex;
  /* some shared data */
  int eventCounter;
} SharedData;

SharedData globalSharedData;

typedef struct {
  /* details unimportant */
} NewData;

void newData(NewData data) {
  int localCopyOfCounter;

  if (/* information contained in new data triggers an
         event */) {
    pthread_mutex_lock(&globalSharedData.mutex);
    localCopyOfCounter = ++globalSharedData.eventCounter;
    pthread_mutex_unlock(&globalSharedData.mutex);
  }
  else {
    return;
  }

  /* Perform long running computation. */

  if (localCopyOfCounter != globalSharedData.eventCounter) {
    /* A new event has happened, old information is stale and
       the current computation can be aborted. */
    return;
  }

  /* Perform another long running computation whose results
     depend on the previous one. */

  if (localCopyOfCounter != globalSharedData.eventCounter) {
    /* Another check for new event that causes information
       to be stale. */
    return;
  }

  /* Final stage of computation whose results depend on two
     previous stages. */
}

Существует пул потоков, обслуживающих соединение для входящих данных, поэтому одновременно могут быть запущены несколько экземпляров newData. В многопроцессорной среде есть две проблемы, с которыми я сталкиваюсь при правильной обработке части кода этого счетчика: предотвращение кеширования компилятором копии общего счетчика в регистре, чтобы другие потоки не могли ее увидеть, и форсирование Процессор своевременно записывает данные о значении счетчика в память, чтобы другие потоки могли его увидеть. Я бы предпочел не использовать вызов синхронизации вокруг проверок счетчика, поскольку допустимо частичное чтение значения счетчика (оно будет давать значение, отличное от локальной копии, что должно быть достаточным для заключения о том, что событие произошло). Было бы достаточно объявить поле eventCounter в SharedData как изменчивое, или мне нужно сделать что-то еще здесь? И есть ли лучший способ справиться с этим?

Ответы [ 3 ]

2 голосов
/ 16 мая 2009

К сожалению, стандарт C очень мало говорит о параллелизме. Тем не менее, большинство компиляторов (gcc и msvc, в любом случае) будут считать энергозависимое чтение так, как будто получает семантику - переменная энергозависимости будет перезагружаться из памяти при каждом доступе. Желательно, чтобы ваш код в том виде, в каком он есть сейчас, может сравнивать значения, кэшированные в регистрах. Я даже не удивлюсь, если оба сравнения будут оптимизированы.

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

int load_acquire(volatile int * counter) { return *counter; }

if (localCopy != load_acquire(&sharedCopy))
    // ...
0 голосов
/ 16 мая 2009

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

volatile - бесполезный в наши дни, по большей части, вы должны смотреть на барьеры памяти, которые являются другим средством низкоуровневого ЦП, чтобы помочь с многоядерными конфликтами.

Тем не менее, лучший совет, который я могу дать, заключается в том, чтобы вы разбирались в различных управляемых и собственных многоядерных библиотеках поддержки. Я думаю, что некоторые из более старых, например OpenMP или MPI (основанные на сообщениях), все еще работают, и люди будут рассказывать о том, насколько они круты ... однако для большинства разработчиков что-то вроде TBB или * 1011 от intel * Microsoft новый API, я также только что откопал этот проект кода статья , он, очевидно, использует cmpxchg8b, который является низкоуровневым аппаратным маршрутом, о котором я упоминал изначально ...

Удачи.

0 голосов
/ 16 мая 2009

предотвращение кеширования компилятором копия локального счетчика в реестре поэтому другие темы не видят его

Ваша локальная копия счетчика является "локальной", созданной в стеке выполнения и видимой только для запущенного потока. Каждый другой поток выполняется в другом стеке и имеет собственную локальную переменную-счетчик (без параллелизма).

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

...