Можно ли, чтобы несколько потоков записывали одинаковые значения в одни и те же переменные? - PullRequest
3 голосов
/ 16 сентября 2008

Я понимаю об условиях гонки и о том, как с несколькими потоками, обращающимися к одной и той же переменной, обновления, сделанные одним, могут игнорироваться и перезаписываться другими, но что если каждый поток записывает одно и то же значение (не разные значения) в одну и ту же переменную; может ли это даже вызвать проблемы? Может ли этот код:

GlobalVar.property = 11;

(при условии, что свойству никогда не будет назначено ничего, кроме 11), вызвать проблемы, если несколько потоков выполнят его одновременно?

Ответы [ 7 ]

8 голосов
/ 16 сентября 2008

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

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

int x = 0, y = 0;

//thread A does:
x = 1;
y = 2;
if (y == 2)
    print(x);

//thread B does, at the same time:
if (y == 2)
    print(x);

Поток A всегда будет печатать 1, но для потока B он будет печататься полностью. Порядок операций в потоке A требуется наблюдать только из кода, выполняемого в потоке A - поток B может видеть любую комбинацию штат. Операции записи в x и y могут не выполняться по порядку.

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

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

2 голосов
/ 16 сентября 2008

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

Я случайно написал код, который делал что-то подобное с типами POD (встроенные примитивные типы), и он работал нормально - однако, это определенно не очень хорошая практика, и я не уверен, что он надежен.

Почему бы просто не заблокировать память вокруг этой переменной, когда вы ее используете? На самом деле, если вы каким-то образом «знаете», что это единственный оператор записи, который может произойти в какой-то момент в вашем коде, почему бы просто не использовать значение 11 напрямую, а не записывать его в общую переменную? ( edit: Полагаю, лучше использовать постоянное имя вместо magic number 11 непосредственно в коде, кстати.)

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

1 голос
/ 21 сентября 2008

Вот мой взгляд на вопрос.

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

bool flag = false
threadContainer tc
threadInputs inputs

check(input)
{
    ...do stuff to input
    if(success)
        flag = true
}

start multiple threads
foreach(i in inputs) 
   t = startthread(check, i)
   tc.add(t)  // Keep track of all the threads started

foreach(t in tc)
    t.join( )  // Wait until each thread is done

if(flag)
   print "One of the threads were successful"
else
   print "None of the threads were successful"

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

1 голос
/ 16 сентября 2008

В общем, это не считается безопасным делом, если ваша система не обеспечивает атомарную работу (операции, которые гарантированно будут выполнены за один цикл). Причина в том, что хотя оператор «C» выглядит простым, часто происходит ряд базовых операций сборки.

В зависимости от вашей ОС вы можете сделать несколько вещей:

  • Возьмите семафор взаимного исключения (мьютекс) для защиты доступа
  • в некоторых ОС вы можете временно отключить вытеснение, которое гарантирует, что ваш поток не будет заменен.
  • Некоторые ОС предоставляют семафор писателя или читателя, который является более производительным, чем обычный старый мьютекс.
1 голос
/ 16 сентября 2008

Я ожидаю, что результат будет неопределенным. Как это может варьироваться от компилятора к компилятору, от языка к языку и от ОС к ОС и т. Д. Так что нет, это не безопасно

ПОЧЕМУ вы хотели бы сделать это, хотя - добавление строки для получения блокировки мьютекса - это всего одна или две строки кода (в большинстве языков), и это устранит любую возможность возникновения проблемы. Если это будет в два раза дороже, то вам нужно найти альтернативный способ решения проблемы

0 голосов
/ 21 сентября 2008

Если предположить, что свойству никогда не будет присвоено ничего, кроме 11, тогда я не вижу причины для присвоения в первую очередь. Просто сделайте это константой тогда.

Назначение имеет смысл, только если вы намереваетесь изменить значение , если только сам акт присвоения не имеет других побочных эффектов - например, изменчивые записи имеют побочные эффекты видимости памяти в Java. А если вы изменяете состояние, совместно используемое несколькими потоками, вам необходимо синхронизировать или иным образом «обработать» проблему параллелизма.

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

Компиляторы, JIT, кэши ЦП. Все они пытаются сделать ваш код максимально быстрым, и если вы не предъявляете никаких явных требований к видимости памяти, они воспользуются этим. Если не на вашей машине, то кто-то еще.

0 голосов
/ 16 сентября 2008

Если операция атомарная, вы должны нормально справиться. Но я бы не стал этого делать на практике. Лучше просто получить блокировку на объекте и записать значение.

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