Что подразумевается под & ldquo; потокобезопасным & rdquo; код?

Отправка кодировки в заголовки всегда хорошая идея.

Если это невозможно, тэг

17 ответов

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

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

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

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

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

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

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

Локальное хранилище потока:

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

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

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

читать дальше:

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


246
ответ дан Adam Millerchip 23 March 2015 в 14:22
поделиться

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

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.

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

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

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

0
ответ дан rghome 23 March 2015 в 14:22
поделиться

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

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

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

0
ответ дан shabby 23 March 2015 в 14:22
поделиться

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

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

1
ответ дан Steve Knight 23 March 2015 в 14:22
поделиться

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

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

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

20
ответ дан Yangshun Tay 23 March 2015 в 14:22
поделиться

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

22
ответ дан Mnementh 23 March 2015 в 14:22
поделиться

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

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

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

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

Поточно-ориентированная программа гарантирует согласованность памяти [тысяча сто сорок-два]. [+1135]

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

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

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

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

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

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

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

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

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

26
ответ дан Marcus Downing 23 March 2015 в 14:22
поделиться

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

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

32
ответ дан Buu Nguyen 23 March 2015 в 14:22
поделиться

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

8
ответ дан Alireza Fattahi 23 March 2015 в 14:22
поделиться

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

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. Четвертое и последнее условие, которое должно произойти для гонки (и, следовательно, для кода НЕ быть «поточно-ориентированным»), заключается в том, что другой поток должен быть в состоянии чтобы получить доступ к общей памяти , а инвариант нарушается, вызывая, таким образом, непоследовательное или неправильное поведение.
47
ответ дан Charles Bretana 23 March 2015 в 14:22
поделиться

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

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

75
ответ дан Marek Blotny 23 March 2015 в 14:22
поделиться

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

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

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

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

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

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

  • больших затрат времени выполнения (уже упоминавшихся другими ) в приложениях, которые перебирают эти «потокобезопасные» структуры.
  • Thread.sleep (5000) должен спать в течение 5 секунд. Однако, если кто-то изменит системное время, вы можете спать очень долго или вообще не спать. ОС записывает время пробуждения в абсолютной форме, а не в относительной.
3
ответ дан VonC 23 March 2015 в 14:22
поделиться

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

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

4
ответ дан tvanfosson 23 March 2015 в 14:22
поделиться

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

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

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

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

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

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

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

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

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

8
ответ дан Community 23 March 2015 в 14:22
поделиться

Да и нет.

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

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

9
ответ дан Bill the Lizard 23 March 2015 в 14:22
поделиться

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

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

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

Наборы Терминологии

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

  1. ориентированный на многопотоковое исполнение
  2. не ориентированный на многопотоковое исполнение

второе (и лучше):

  1. доказательство потока
  2. распараллеливает совместимый
  3. , распараллеливают враждебный

, одна треть:

  1. внутренне синхронизировался
  2. , внешне синхронизировался
  3. аналогии unsynchronizeable

ориентированный на многопотоковое исполнение ~ доказательство потока , ~ внутренне синхронизировался

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

не ориентированный на многопотоковое исполнение (но хороший) ~ распараллеливают совместимый , ~ внешне синхронизировался

предположим, что Вы переходите к банку. Существует строка, т.е. конкуренция для кассиров банка. Поскольку Вы не дикарь, Вы распознаете, что лучшая вещь сделать посреди конкуренции для ресурса состоит в том, чтобы поставить в очередь как цивилизованное существо. Никто технически не заставляет Вас сделать это. Мы надеемся, что у Вас есть необходимое социальное программирование, чтобы сделать это самостоятельно. В этом смысле лобби банка , внешне синхронизировался. мы должны сказать, что это небезопасно потоком? это - то, что - импликация то, если Вы идете с ориентированный на многопотоковое исполнение , небезопасный потоком биполярный набор терминологии. Это не очень хороший набор условий. Лучшая терминология , внешне синхронизировался, Это не враждебно к тому, чтобы быть полученным доступ несколькими клиентами, но это не делает работы синхронизации их также. Клиенты делают это сами.

не ориентированный на многопотоковое исполнение (и плохо) ~ распараллеливают враждебный ~ unsynchronizeable

пример враждебный к потоку , система - при вхождении в отношения с кем-то, кто изменяет Вам, потому что, даже если Вам говорят, у Вас есть эксклюзивный доступ к ним глубоко, это не верно, и доверительный повреждается. Повреждение "данных" включено. Разрешение почти невозможно, потому что система становится не детерминированной, ведя к разбивке значения в межличностном общении, точно так же, как с потоками. Этот шаблон может также считаться более широко [1 127] антиобщественный, , который я предпочитаю, потому что это менее характерно для потоков и так в более общем плане применимо ко многим областям программирования.

, Почему ориентированный на многопотоковое исполнение и др. плохая терминология, устанавливает

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

Напоминание наших целей

По существу, наша цель состоит в том, чтобы ниспровергать хаос.

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

Синхронизация потоков об увеличении порядка и уменьшении хаоса. Уровни, на которых Вы делаете это, соответствуют упомянутым выше условиям. Высший уровень означает, что система ведет себя совершенно предсказуемым способом каждый раз. Второй уровень означает, что система ведет себя достаточно хорошо, что код вызова может надежно обнаружить непредсказуемость. Например, побочный пробуждение в условной переменной или отказе заблокировать взаимное исключение, потому что это не готово. Третий уровень означает, что система не ведет себя достаточно хорошо для проигрывания с кем-либо еще и может только КОГДА-ЛИБО выполняться однопоточная, не подвергаясь хаосу.

1
ответ дан 23 November 2019 в 00:37
поделиться