pthreads: если я увеличиваю глобал из двух разных потоков, могут ли быть проблемы с синхронизацией? - PullRequest
4 голосов
/ 09 сентября 2010

Предположим, у меня есть два потока A и B, которые оба увеличивают глобальную переменную "count". Каждый поток выполняет цикл for, как этот:

for(int i=0; i<1000; i++)
    count++; //alternatively, count = count + 1;

т.е. каждый поток увеличивает счет в 1000 раз, и, скажем, счет начинается с 0. Могут ли быть проблемы с синхронизацией в этом случае? Или будет правильно считать 2000, когда выполнение закончено? Я полагаю, поскольку утверждение "count = count + 1" может разбиться на ДВЕ инструкции по сборке, существует ли вероятность того, что другой поток будет заменен между этими двумя инструкциями? Точно сказать не могу. Что ты думаешь?

Ответы [ 4 ]

11 голосов
/ 09 сентября 2010

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

Пример использования мьютексов pthread

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

for(int i=0; i<1000; i++) {
    pthread_mutex_lock(&mutex);
    count++;
    pthread_mutex_unlock(&mutex);
}

Использование атомных операций

Здесь предварительно обсуждаются конкретные атомные операции платформы: UNIX Переносимые атомарные операции

Если вам требуется только поддержка GCC, этот подход прост. Если вы поддерживаете другие компиляторы, вам, вероятно, придется принимать некоторые решения для каждой платформы.

3 голосов
/ 09 сентября 2010

Да, могут быть проблемы с синхронизацией.

В качестве примера возможных проблем нет никакой гарантии, что само приращение является атомарной операцией.

Другими словами, если один поток читает значение для приращения, а затем выгружается, другой поток может войти и изменить его, тогда первый поток напишет неправильное значение:

+-----+
|   0 | Value stored in memory (0).
+-----+
|   0 | Thread 1 reads value into register (r1 = 0).
+-----+
|   0 | Thread 2 reads value into register (r2 = 0).
+-----+
|   1 | Thread 2 increments r2 and writes back.
+-----+
|   1 | Thread 1 increments r1 and writes back.
+-----+

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

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

Если у вас есть атомарные операции, которые гарантированно будут работать в вашей реализации, вы можете использовать их,В противном случае используйте взаимные исключения. Это то, что pthreads обеспечивает для синхронизации (и гарантии будут работать), так что самый безопасный подход.

2 голосов
/ 09 сентября 2010

Счет явно должен быть защищен мьютексом или другим механизмом синхронизации.

На фундаментальном уровне статус count ++ разбивается на:

load count into register
increment register
store count from register

Переключение контекста может произойти до / после любого из этих шагов, что приводит к ситуациям, подобным:

Thread 1:  load count into register A (value = 0)
Thread 2:  load count into register B (value = 0)
Thread 1:  increment register A (value = 1)
Thread 1:  store count from register A (value = 1)
Thread 2:  increment register B (value = 1)
Thread 2:  store count from register B (value = 1)

Как видите, оба потока завершили одну итерацию цикла, но в результате счетчик увеличился только один раз.

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

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

1 голос
/ 24 февраля 2019

Я полагаю, поскольку выражение "count = count + 1" может разбиться на ДВЕ инструкции по сборке, существует ли вероятность того, что другой поток может быть заменен между этими двумя инструкциями? Точно сказать не могу. Что ты думаешь?

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

Стандарт pthreads не определяет поведение, когда один поток обращается к объекту, в то время как другой поток изменяет или может изменять его. Поэтому, если вы не пишете код, специфичный для платформы, вы должны предполагать, что этот код может делать что угодно - даже сбой.

Очевидное исправление pthreads - использование мьютексов. Если на вашей платформе есть атомарные операции, вы можете использовать их.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...