Потокобезопасное программирование - PullRequest
7 голосов
/ 05 марта 2012

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

Кроме того, предположим, что у меня есть 2 потока, один из которых записывает в структуру, а другой читает из нее.Это опасно в любом случае?Есть ли что-то, что я должен искать?Я не думаю, что это проблема.Оба потока не будут (ну и не могут) получать доступ к структуре в одно и то же время.

Также, может кто-нибудь подскажет, как в этом примере: https://stackoverflow.com/a/5125493/1248779 мы делаем лучшеработа в вопросах параллелизма.Я не понимаю.

Ответы [ 7 ]

7 голосов
/ 05 марта 2012

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

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

Простым примером будет структура 'point' для 2dграфика.Вы хотели бы переместить точку с [2,2] на [5,6].Если бы у вас была другая нить, рисующая линию к этой точке, вы могли бы очень легко нарисовать [5,2].

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

  1. Ой, я только что прочитал эту вещь в противоречивом состоянии.
  2. О, ятолько что изменил эту вещь из 2 потоков, и теперь это мусор.
  3. Yay!Я узнал о блокировках
  4. Ух, у меня много блокировок, и кажется, что все просто зависает, когда у меня много блокировок во вложенном коде.
  5. Хмм.Мне нужно перестать делать эту блокировку на лету, мне кажется, я скучаю по многим местам;поэтому я должен заключить их в структуру данных.
  6. Эта структура данных была замечательной, но теперь я, кажется, все время блокирую, и мой код работает так же медленно, как один поток.
  7. условные переменные странные
  8. Это быстро, потому что я стал умным, как блокировать вещи.Хмм.Иногда данные портятся.
  9. Вау .... InterlockedWhatDidYouSay?
  10. Эй, не смотри, я делаю эту вещь, называемую спин-блокировкой.
  11. Переменные условия.Хм ... Понятно.
  12. Вы знаете, что, как насчет того, что я просто начинаю думать о том, как работать с этими вещами совершенно независимо, упорядочивая свои операции и имея как можно меньше межпоточных зависимостей ....

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

4 голосов
/ 05 марта 2012

Потокобезопасность является одним из аспектов более широкого круга вопросов под общим заголовком «Параллельное программирование».Я бы предложил почитать эту тему.

Ваше предположение, что два потока не могут получить доступ к структуре одновременно, не является правильным.Первое: сегодня у нас многоядерные машины, поэтому два потока могут работать одновременно.Второе: даже на одноядерном компьютере отрезки времени, выделенные любому другому потоку, непредсказуемы.Вы должны ожидать, что муравей в любое произвольное время может обрабатывать «другой» поток.См. Мой пример «окна возможностей» ниже.

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

  { foreground: red; background: black }

и писатель находится в процессе изменения этих

   foreground = black;
            <=== window of opportunity
   background = red;

Если читатель читает значения только в этом окне возможностей, тогда онвидит «бессмысленную» комбинацию

  { foreground: black; background: black }

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

Следовательно, мы используем API CriticalSection, упомянутые Стефаном, чтобы предотвратить поток, видящий несовместимое состояние.

3 голосов
/ 05 марта 2012

что это такое?

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

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

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

boost / libs / thread /, вероятно, будет хорошим введением.Тема довольно сложная.

Стандартная библиотека C ++ 11 предоставляет реализации для блокировок, атомарных объектов и потоков - любые хорошо написанные программы, использующие их, были бы полезны для чтения.Стандартная библиотека была смоделирована после реализации boost.

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

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

1 голос
/ 05 марта 2012

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

  • Нет неопределенного поведения
  • Все потоки гарантированно соблюдают все инварианты структур данных

Фактические операции A и B важны.Если два потока читают обычную переменную int, то это нормально.Однако, если какой-либо поток может записать в эту переменную, и нет синхронизации, чтобы гарантировать, что чтение и запись не могут происходить вместе, тогда у вас есть гонка данных, которая является неопределенным поведением, и это не потокобезопасен.

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

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

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

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

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

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

1 голос
/ 05 марта 2012

При написании многопоточных программ на C ++ на платформах WIN32 вам необходимо защищать определенные общие объекты, чтобы только один поток мог получить к ним доступ в любой момент времени из разных потоков. Вы можете использовать 5 системных функций для достижения этой цели. Это InitializeCriticalSection, EnterCriticalSection, TryEnterCriticalSection, LeaveCriticalSection и DeleteCriticalSection.

Также, возможно, эти ссылки могут помочь: как сделать поток приложений безопасным?

http://www.codeproject.com/Articles/1779/Making-your-C-code-thread-safe

0 голосов
/ 05 марта 2012

Чтобы ответить на вторую часть вопроса: представьте, что два потока обращаются к std::vector<int> data:

//first thread
if (data.size() > 0)
{
   std::cout << data[0]; //fails if data.size() == 0
}

//second thread
if (rand() % 5 == 0)
{
   data.clear();
}
else
{
   data.push_back(1);
}

Запустите эти потоки параллельно, и ваша программа потерпит крах, потому что std::cout << data[0]; может выполняться сразу после data.clear();.

Вы должны знать, что в любой точке кода вашего потока поток может быть прерван, например, после проверки этого (data.size() > 0), и другой поток может стать активным. Хотя первый поток выглядит правильно в однопоточном приложении, он не в многопоточной программе.

0 голосов
/ 05 марта 2012

Потоки безопасны, когда определенный блок кода защищен от доступа более чем одним потоком.Это означает, что манипулируемые данные всегда остаются в согласованном состоянии.

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

...