Что означает "потокобезопасный" код?

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

15 ответов


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

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

...

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

входы

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

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

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

локального потока хранение

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

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

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

подробнее :

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



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

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


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

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

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

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


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


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

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

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


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

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

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


да и нет.

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

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


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

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

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

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

изловчился

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

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

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

Примечание: кросс-пост Программисты!--5-->


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


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

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


для завершения других ответов:

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

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

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

планирование потоков не гарантируется раунд-Робин. Задача может полностью завладеть ЦП за счет потоков того же приоритета. Вы можете использовать поток.yield () иметь совесть. Вы можете использовать (в java) поток.метод setpriority(резьба.NORM_PRIORITY-1) понизить приоритет потока

плюс берегитесь из:

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

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

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

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

что делает threadsafe в смысле?

Потокобезопасная программа гарантирует согласованность памяти.

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

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

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

на synchronized и volatile конструкции, а также Thread.start() и Thread.join() методов, может образоваться происходит-перед отношения.

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

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

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

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


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

*условия

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