Что подразумевается под "потокобезопасным" кодом?

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

Ответ 1

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

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

Есть несколько способов добиться безопасности потоков:

Re-entrancy:

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

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

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

Потоковое локальное хранилище:

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

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

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

прочитайте больше:

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


Ответ 2

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

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

Ответ 3

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

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
  • Первое условие состоит в том, что есть ячейки памяти, доступные из более чем одного потока. Как правило, эти местоположения являются глобальными/статическими переменными или являются памятью кучи, доступной из глобальных/статических переменных. Каждый поток получает собственный кадр стека для локальных переменных с локальными функциями/методами, поэтому эти локальные переменные функции/метода otoh (которые находятся в стеке) доступны только из одного потока, которому принадлежит этот стек.
  • Второе условие - свойство (часто называемое инвариантом), которое связано с этими ячейками разделяемой памяти, должно быть истинным или действительным для правильной работы программы. В приведенном выше примере свойство состоит в том, что "totalRequests должно точно представлять общее количество раз, когда какой-либо поток выполнил какую-либо часть инструкции инкремента". Как правило, это свойство инварианта должно быть истинным (в этом случае totalRequests должен содержать точный счет) до того, как обновление будет выполнено для правильного обновления.
  • Третье условие состоит в том, что свойство инварианта НЕ выполняется в течение некоторой части фактического обновления. (Это временно недействительно или ложно во время некоторой части обработки). В этом конкретном случае с момента, когда totalRequests выбирается до момента сохранения обновленного значения, totalRequests не удовлетворяет инварианту.
  • Четвертое и заключительное условие, которое должно произойти для гонки (и для кода, поэтому НЕ быть "потокобезопасным" ), заключается в том, что другой поток должен иметь доступ к общей памяти в то время как инвариант нарушается, что вызывает противоречивое или неправильное поведение.

Ответ 4

Мне нравится определение от Brian Goetz Java Concurrency в Практике за его полноту

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

Да и нет.

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

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

Ответ 9

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

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

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

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

Проприведенный пример

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

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

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

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

Ответ 10

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

Ответ 11

Я хотел бы добавить дополнительную информацию поверх других хороших ответов.

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

Посмотрите на этот вопрос SE для более подробной информации:

Что означает потокобезопасность?

Защищенная от вирусов программа гарантирует целостность памяти.

Из документации oracle страница в расширенном параллельном API:

Свойства согласованности памяти:

Глава 17 Спецификации языка Java ™ определяет отношение "происходить до" в операциях памяти, такое как чтение и запись общих переменных. Результаты записи одним потоком гарантированно будут видны для чтения другим потоком только в том случае, если операция записи происходит до операции чтения.

Конструкции synchronized и volatile, а также методы Thread.start() и Thread.join() могут формироваться как-либо до отношений.

Методы всех классов в java.util.concurrent и его подпакетах распространяют эти гарантии на синхронизацию более высокого уровня. В частности:

  • Действия в потоке перед помещением объекта в любой параллельный сбор произойдут - перед действиями после доступа или удаления этого элемента из коллекции в другом потоке.
  • Действия в потоке перед представлением Runnable в Executor происходят до того, как начнется его выполнение. Аналогично для Callables, отправленных на ExecutorService.
  • Действия, предпринятые асинхронным вычислением, представленными действиями Future произошедшие после поиска после Future.get() в другом потоке.
  • Перед тем, как "освободить" методы синхронизации, такие как Lock.unlock, Semaphore.release, and CountDownLatch.countDown, произойдут до действий после успешного метода "получения", например Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await, в одном и том же объекте синхронизатора в другом потоке.
  • Для каждой пары потоков, которые успешно обмениваются объектами через Exchanger, действия до exchange() в каждом потоке происходят до тех, которые следуют за соответствующим обменом() в другом потоке.
  • Действия до вызова CyclicBarrier.await и Phaser.awaitAdvance (а также его варианты) происходят до действий, выполняемых действием барьера, и действия, выполняемые действием барьера, происходят до действий после успешного возвращения из соответствующего ожидание в других потоках.

Ответ 12

Не путайте безопасность потока с детерминизмом. Потокобезопасный код также может быть недетерминированным. Учитывая сложность отладки проблем с потоковым кодом, это, вероятно, нормальный случай.: -)

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

Ответ 13

Чтобы выполнить другие ответы:

Синхронизация - это только проблема, когда код в вашем методе выполняет одну из двух задач:

  • работает с некоторым внешним ресурсом, который не является потокобезопасным.
  • Считывает или изменяет постоянный объект или поле класса

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

Планирование потоков не гарантируется round-robin. Задача может полностью запустить процессор за счет потоков с одинаковым приоритетом. Вы можете использовать Thread.yield(), чтобы иметь совесть. Вы можете использовать (в java) Thread.setPriority(Thread.NORM_PRIORITY-1), чтобы снизить приоритет потока

Плюс остерегайтесь:

  • большая стоимость исполнения (уже упоминаемая другими) в приложениях, которые перебирают эти "потокобезопасные" структуры.
  • Thread.sleep(5000) должен спать в течение 5 секунд. Однако, если кто-то изменяет системное время, вы можете спать очень долго или совсем не время. OS записывает время пробуждения в абсолютной форме, а не относительной.

Ответ 14

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

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

Ответ 15

Простыми словами

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

Ответ 16

В простейших словах: P  Если безопасно выполнять несколько потоков на блоке кода, это безопасно для потоков *

* применяются условия

Условия упоминаются другими ответами типа 1. Результат должен быть таким же, если вы выполняете один поток или несколько потоков по нему и т.д.

Ответ 17

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

Ответ 18

Давайте ответим на это примером:

class NonThreadSafe {

    private int counter = 0;

    public boolean countTo10() {
        count = count + 1;
        return (count == 10);
    }

Метод countTo10 добавляет единицу к счетчику, а затем возвращает true, если счетчик достиг 10. Он должен возвращать true только один раз.

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

Например, если count начинается с 9, один поток может добавить 1 к счету (делая 10), но затем второй поток может ввести метод и снова добавить 1 (делая 11), прежде чем первый поток сможет выполнить сравнение с 10 Затем оба потока выполняют сравнение и обнаруживают, что счетчик равен 11, и ни один из них не возвращает значение true.

Так что этот код не является потокобезопасным.

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

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