Что подразумевается под «потокобезопасным» кодом? - PullRequest
327 голосов
/ 04 ноября 2008

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

Ответы [ 16 ]

242 голосов
/ 04 ноября 2008

Из Википедии:

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

Существует несколько способов обеспечения безопасности потоков:

Повторный вход:

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

Взаимное исключение:

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

Нить локального хранилища:

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

Атомные операции:

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

Подробнее:

http://en.wikipedia.org/wiki/Thread_safety


73 голосов
/ 04 ноября 2008

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

http://mindprod.com/jgloss/threadsafe.html

47 голосов
/ 04 ноября 2008

Более информативный вопрос заключается в том, что делает код не защищенным от потоков, и ответ состоит в том, что существует четыре условия, которые должны быть выполнены ... Представьте себе следующий код (и его перевод на машинный язык)

totalRequests = totalRequests + 1
MOV EAX, [totalRequests]   // load memory for tot Requests into register
INC EAX                    // update register
MOV [totalRequests], EAX   // store updated value back to memory
  1. Первым условием является наличие областей памяти, доступных из нескольких потоков. Как правило, эти местоположения являются глобальными / статическими переменными или являются доступными для динамической памяти из глобальных / статических переменных. Каждый поток получает свой собственный кадр стека для локальных переменных в области функций / методов, поэтому эти локальные переменные функции / метода otoh (которые находятся в стеке) доступны только из одного потока, которому принадлежит этот стек.
  2. Второе условие состоит в том, что существует свойство (часто называемое инвариант ), которое связано с этими расположениями общей памяти, которые должны быть истинными или действительными, чтобы программа функционировала правильно. В приведенном выше примере свойство заключается в том, что « totalRequests должно точно представлять общее количество раз, когда какой-либо поток выполнял какую-либо часть инструкции приращения ». Как правило, это свойство инварианта должно содержать значение true (в этом случае totalRequests должно содержать точное число), прежде чем произойдет обновление, чтобы обновление было корректным.
  3. Третье условие заключается в том, что свойство инварианта НЕ сохраняется во время какой-либо части фактического обновления. (Это временно недействительно или ложно во время некоторой части обработки). В этом конкретном случае с момента извлечения totalRequests до момента сохранения обновленного значения totalRequests не удовлетворяет инварианту.
  4. Четвертое и последнее условие, которое должно произойти для гонки (и, следовательно, для кода НЕ быть «потокобезопасным»), заключается в том, что другой поток должен быть в состоянии для доступа к общей памяти , в то время как инвариант нарушается, вызывая, таким образом, непоследовательное или неправильное поведение.
32 голосов
/ 04 ноября 2008

Мне нравится определение из Java-параллелизма Брайана Гетца на практике из-за его полноты

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

26 голосов
/ 04 ноября 2008

Как уже отмечали другие, безопасность потоков означает, что фрагмент кода будет работать без ошибок, если он используется более чем одним потоком одновременно.

Стоит помнить, что иногда это требует затрат компьютерного времени и более сложного кодирования, поэтому это не всегда желательно. Если класс можно безопасно использовать только в одном потоке, лучше сделать это.

Например, в Java есть два почти эквивалентных класса: StringBuffer и StringBuilder. Разница в том, что StringBuffer является потокобезопасным, поэтому один экземпляр StringBuffer может использоваться несколькими потоками одновременно. StringBuilder не является поточно-ориентированным и предназначен для замены более высокой производительности в тех случаях (в подавляющем большинстве), когда строка строится только одним потоком.

22 голосов
/ 04 ноября 2008

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

20 голосов
/ 04 ноября 2008

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

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

  • Дедлок, вызванный взаимной зависимостью от общей переменной
    Если у вас есть две общие переменные A и B. В одной функции вы сначала блокируете A, а затем блокируете B. В другой функции вы начинаете блокировать B и через некоторое время блокируете A. Это потенциальная тупиковая ситуация, когда первая функция будет дождитесь разблокировки B, когда вторая функция будет ожидать разблокировки A. Эта проблема, вероятно, не будет возникать в вашей среде разработки и только время от времени. Чтобы избежать этого, все блокировки всегда должны быть в одном и том же порядке.

9 голосов
/ 04 ноября 2008

Да и нет.

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

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

8 голосов
/ 07 февраля 2013

В сущности, в многопоточной среде многие вещи могут пойти не так (переупорядочивание инструкций, частично построенные объекты, одна и та же переменная, имеющая разные значения в разных потоках из-за кэширования на уровне ЦП и т.

Мне нравится определение, данное Параллелизмом Java на практике :

[Часть кода] является поточно-ориентированной, если она ведет себя корректно при доступе из нескольких потоков, независимо от планирования или чередования выполнения этих потоков средой выполнения, и без дополнительной синхронизации или другой координации на часть телефонного кода.

Под правильно они означают, что программа ведет себя в соответствии со своими спецификациями.

Придуманный пример

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

  • counter.next() никогда не возвращает значение, которое уже было возвращено ранее (для простоты мы не предполагаем переполнения и т. Д.)
  • все значения от 0 до текущего значения были возвращены на некотором этапе (никакое значение не пропускается)

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

Примечание: кросс-пост на программистов

7 голосов
/ 04 ноября 2008

Проще - код будет работать нормально, если многие потоки исполняют этот код одновременно.

...