C ++ Threading Вопрос - PullRequest
       12

C ++ Threading Вопрос

3 голосов
/ 22 мая 2009

У меня есть объект Foo, который имеет глобальную переменную, Time currentTime

Foo имеет два метода, которые вызываются из разных потоков.

update()
{
    currentTime = currentTime + timeDelay;
}

restart(Time newTime)
{
    currentTime = newTime;
}

Я наблюдаю поведение при перезапуске, время меняется корректно, и в других случаях, когда currentTime, похоже, не сбрасывается (или сбрасывается, но затем обновляется, как-то сбрасывает его.

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

Ответы [ 7 ]

9 голосов
/ 22 мая 2009

У вас, конечно, здесь есть состояние гонки. Самое прямолинейное решение - защитить использование общей переменной currentTime с помощью блокировки. Я использую класс мьютекса Boost.Threads здесь:

class Foo
{
  boost::mutex _access;
  update()
  {
    boost::mutex::scoped_lock lock(_access);
    currentTime = currentTime + timeDelay;
  }

  restart(Time newTime)
  {
    boost::mutex::scoped_lock lock(_access);
    currentTime = newTime;
  }
};
1 голос
/ 22 мая 2009

Поток 1 вызывает update, получает копию currentTime и сохраняет ее в своей локальной памяти потока. Поток 2 вызывает перезапуск, устанавливает currentTime в newTime. Нить 2 заканчивается. Поток 1 продолжается, переназначает currentTime для currentTime в его локальной памяти потока (который является старым значением currentTime до вашего вызова перезапуска) + timeDelay. Тема 1 теперь заканчивается.

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

Вы должны использовать мьютекс, предложенный другими.

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

Если currentTime действительно глобальная переменная , как вы и говорите, вам понадобится глобальный мьютекс для защиты переменной. (PTHREAD_MUTEX_INITIALIZER или конструкция BOOST.call_once)

В этом случае пример BOOST.Threads неверен, потому что два экземпляра класса Foo, живущие в разных потоках, будут иметь разные экземпляры мьютекса _access (я действительно не предпочитаю _prex!) И заблокируют свой собственный экземпляр и не защищать переменную currentTime.

Если currentTime является переменной экземпляра, пример BOOST.Threads верный.

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

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

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

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

Использование мьютекса кажется мне слишком тяжелым в этой ситуации. Вместо этого я бы использовал InterlockedExchange и InterlockedAdd.

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

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

update()
{
    // Lock mutex
    currentTime = currentTime + timeDelay;
    // unlock mutex
}
// Same idea for restart()

Проблема в том, что доступ к одной и той же переменной без примитива синхронизации, такого как мьютекс, проблематичен с потоками. Скажем, update () читает currentTime, выполняет сложение, но прежде чем он сможет сохранить результат, мы переключаем потоки, restart () делает свое дело. Теперь мы переключаемся на update (), который записывает (теперь недействительный) результат добавления обратно в currentTime, перезаписывая работу restart (). Mutex предотвращает это, позволяя вам гарантировать, что операция является атомной. Google для некоторых учебных пособий - вам нужно знать много других вещей, таких как тупики.

Как именно вы создаете / блокируете мьютекс, зависит от вашей ОС / каких библиотек вы хотите использовать. Родные решения - это pthreads на * nix системах, критические разделы на Win32. (Реализации pthreads существуют для Win32) В библиотеке Boost также есть раздел потоков.

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

Добавьте несколько printfs в каждую функцию, чтобы создать журнал событий.

Например, что вы ожидаете, если update () был выполнен в другом потоке сразу после "currentTime = newTime;"? - или еще хуже - во время назначения в этой строке.

После того, как вы это сделаете, взгляните на: http://en.wikipedia.org/wiki/Mutual_exclusion

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