синхронизация потоков - деликатный вопрос - PullRequest
1 голос
/ 17 декабря 2009

давайте у меня будет этот цикл:

static a;
for (static int i=0; i<10; i++)
{
   a++;
   ///// point A
}

в этот цикл входит 2 темы ...

я в чем-то не уверен .... что произойдет, если thread1 попадет в POINT A, оставайтесь там, пока THREAD2 попадет в цикл 10 раз, но после 10-го цикла после увеличения значения i до 10 перед проверкой значения I, если оно меньше 10, Поток 1 выходит из цикла и, предположим, увеличим i и снова попадем в цикл. какое значение будет увеличивать Thread1 (который я увижу)? это будет 10 или 0?

возможно, что Thread1 увеличит i до 1, а затем поток 2 снова попадет в цикл 9 раз (а может быть, 8, 7 и т. Д.)

спасибо

Ответы [ 11 ]

5 голосов
/ 17 декабря 2009

Если i распределяется между несколькими потоками, все ставки отключены. Любой поток может увеличивать i практически в любой точке во время выполнения другого потока (включая половину операции увеличения этого потока). Не существует осмысленного способа рассуждать о содержании i в приведенном выше коде. Не делай этого. Либо дайте каждому потоку свою собственную копию i, либо сделайте приращение и сравнение с 10 одной атомарной операцией.

5 голосов
/ 17 декабря 2009

Вы должны понимать, что операция инкремента действительно эффективна:

read the value
add 1
write the value back

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

static int a = 0;

thread 1 reads a (0)
adds 1 (value is 1)
thread 2 reads a (0)
adds 1 (value is 1)
thread 1 writes (1)
thread 2 writes (1)

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

Пример, который вы привели, усложняется индексом статического цикла, который я сначала не заметил. Поскольку это код на языке C ++, стандартная реализация состоит в том, что статические переменные видны всем потокам, поэтому для всех потоков существует только одна переменная подсчета циклов. Разумная вещь, которую нужно сделать, это использовать обычную автоматическую переменную, потому что каждый поток будет иметь свою собственную блокировку.

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

3 голосов
/ 17 декабря 2009

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

2 голосов
/ 17 декабря 2009

Я просто собираюсь использовать i++ в вашем цикле:

for (static int i=0; i<10; i++)
{
}

Потому что это имитирует a.(Обратите внимание, static здесь очень странно)

Подумайте, приостановлен ли поток A точно так же, как он достигает i++.Поток B получает i вплоть до 9, переходит в i++ и делает его 10. Если бы он продолжал, цикл существовал бы.Ах, но теперь тема А возобновлена!Таким образом, он продолжается там, где остановился: приращение i!Таким образом, i становится 11, и ваш цикл прерывается.

Каждый раз, когда потоки обмениваются данными, они должны быть защищены.Вы также можете заставить i++ и i < 10 происходить атомарно (никогда не прерываться), если ваша платформа поддерживает это.

1 голос
/ 17 декабря 2009

И именно поэтому в многопоточной среде мы предполагаем использовать блокировки.

В вашем случае вы должны написать:

bool test_increment(int& i)
{
  lock()
  ++i;
  bool result = i < 10;
  unlock();
  return result;
}

static a;
for(static int i = -1 ; test_increment(i) ; )
{
   ++a;
   // Point A
}

Теперь проблема исчезла. Обратите внимание, что lock() и unlock() должны блокировать и разблокировать мьютекс, общий для всех потоков, пытающихся получить доступ к i!

1 голос
/ 17 декабря 2009

Для решения этой проблемы следует использовать взаимное исключение .

0 голосов
/ 08 января 2011

Если вам нужно увеличить значение несколькими потоками одновременно, найдите «атомарные операции». Для linux ищите «атомные операции gcc». На большинстве платформ имеется аппаратная поддержка для атомарного увеличения, добавления, сравнения, замены и многого другого. БЛОКИРОВКА БУДЕТ ПРЕКРАЩЕНА для этого .... атомные inc на величины быстрее, чем lock inc unlock. Если вам нужно изменить много полей одновременно, вам может потребоваться блокировка, хотя вы можете менять поля размером 128 бит за раз с большинством атомарных операций.

volatile - это не то же самое, что атомарная операция. Volatile помогает компилятору знать, когда плохая идея использовать копию переменной. Среди его применений энергозависимость важна, когда у вас есть несколько потоков, изменяющих данные, которые вы хотели бы прочитать «самой последней версией» без блокировки. Volatile по-прежнему не исправит вашу проблему с a ++, поскольку два потока могут одновременно прочитать значение «a», а затем оба увеличивают одно и то же «a», а затем последний, записывающий «a», выигрывает, и вы проигрываете inc. Volatile будет замедлять оптимизированный код, не позволяя компилятору хранить значения в регистрах, а что нет.

0 голосов
/ 11 января 2010

Если ваше "int" не является размером слов атомарного компьютера (например, 64-битный адрес + данные, эмулирующие 32-битную виртуальную машину), вы будете "word-tear". В этом случае ваш "int" составляет 32 бита, но машина адресует 64 атомно. Теперь вы должны прочитать все 64, увеличить половину и записать их обратно.

Это гораздо большая проблема; Не забывайте про наборы команд процессора и grep gcc, чтобы узнать, как он реализует «volatile» везде, если вы действительно хотите узнать подробности.

Добавьте «volatile» и посмотрите, как меняется машинный код. Если вы не смотрите на регистры микросхем, просто используйте библиотеки boost и покончите с этим.

0 голосов
/ 18 декабря 2009

Оба потока имеют свою собственную копию i, поэтому поведение может быть любым. Это одна из причин, почему это такая проблема.

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

И кто-то, несомненно, скажет: «volatile бесполезно в многопоточности» но люди говорят много глупостей. Вам не нужно иметь волатильность, но это полезно для некоторых вещей.

0 голосов
/ 17 декабря 2009

Зачем использовать статический счетчик циклов?

Это пахнет как домашняя работа, и плохая в этом.

...