Означает ли это, что два потока не могут изменить базовые данные одновременно? Или это означает, что данный сегмент кода будет работать с предсказуемыми результатами, когда несколько потоков исполняют этот сегмент кода?
Означает ли это, что два потока не могут изменить базовые данные одновременно? Или это означает, что данный сегмент кода будет работать с предсказуемыми результатами, когда несколько потоков исполняют этот сегмент кода?
Ответы:
Из Википедии:
Потоковая безопасность - это концепция компьютерного программирования, применяемая в контексте многопоточных программ. Часть кода является поточно-ориентированной, если она работает правильно при одновременном выполнении несколькими потоками. В частности, он должен удовлетворять потребность в нескольких потоках для доступа к одним и тем же общим данным, а также необходимость доступа к общему фрагменту данных только одному потоку в любой момент времени.
Есть несколько способов достижения безопасности потока:
Re-entrancy:
Написание кода таким образом, что он может быть частично выполнен одной задачей, повторно введен другой задачей, а затем возобновлен из исходной задачи. Это требует сохранения информации о состоянии в переменных, локальных для каждой задачи, обычно в ее стеке, а не в статических или глобальных переменных.
Взаимное исключение:
Доступ к общим данным сериализуется с использованием механизмов, которые гарантируют, что только один поток читает или записывает общие данные в любое время. Требуется большая осторожность, если фрагмент кода обращается к нескольким совместно используемым частям данных - проблемы включают в себя условия гонки, взаимоблокировки, блокировки в реальном времени, голодание и различные другие проблемы, перечисленные во многих учебниках по операционным системам.
Потоковое локальное хранилище:
Переменные локализованы так, что каждый поток имеет свою собственную личную копию. Эти переменные сохраняют свои значения в пределах подпрограммы и других границ кода и являются поточно-ориентированными, поскольку они локальны для каждого потока, даже если код, который обращается к ним, может быть повторно введен.
Атомные операции:
Доступ к общим данным осуществляется с помощью атомарных операций, которые не могут быть прерваны другими потоками. Обычно это требует использования специальных инструкций машинного языка, которые могут быть доступны в библиотеке времени выполнения. Поскольку операции являются атомарными, общие данные всегда сохраняются в допустимом состоянии независимо от того, какие другие потоки обращаются к ним. Атомарные операции составляют основу многих механизмов блокировки потоков.
читать далее:
http://en.wikipedia.org/wiki/Thread_safety
на немецком языке: http://de.wikipedia.org/wiki/Threadsicherheit
на французском языке: http://fr.wikipedia.org/wiki/Threadsafe (очень короткий)
Потокобезопасный код - это код, который будет работать, даже если многие потоки выполняют его одновременно.
Более информативный вопрос заключается в том, что делает код не защищенным от потоков, и ответ заключается в том, что существует четыре условия, которые должны быть выполнены ... Представьте себе следующий код (и это машинный перевод)
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
Мне нравится определение из параллелизма Java Брайана Гетца на практике для его полноты
«Класс является поточно-ориентированным, если он ведет себя правильно при доступе из нескольких потоков независимо от планирования или чередования выполнения этих потоков средой выполнения и без дополнительной синхронизации или другой координации со стороны вызывающего кода. "
Как отмечали другие, безопасность потоков означает, что фрагмент кода будет работать без ошибок, если он используется более чем одним потоком одновременно.
Стоит помнить, что иногда это требует затрат компьютерного времени и более сложного кодирования, поэтому это не всегда желательно. Если класс можно безопасно использовать только в одном потоке, лучше сделать это.
Например, в Java есть два класса, которые почти эквивалентны, StringBuffer
и StringBuilder
. Разница в том, что StringBuffer
она поточно-ориентированная, поэтому один и тот же экземпляр StringBuffer
может использоваться несколькими потоками одновременно. StringBuilder
не является поточно-ориентированным и предназначен для замены более высокой производительности в тех случаях (в подавляющем большинстве), когда строка строится только одним потоком.
Проще понять, что делает код не потокобезопасным. Есть две основные проблемы, которые заставят потоковое приложение иметь нежелательное поведение.
Доступ к общей переменной без блокировки
Эта переменная может быть изменена другим потоком при выполнении функции. Вы хотите предотвратить это с помощью механизма блокировки, чтобы быть уверенным в поведении вашей функции. Общее эмпирическое правило заключается в том, чтобы сохранить замок в кратчайшие сроки.
Дедлок, вызванный взаимной зависимостью от общей переменной
Если у вас есть две общие переменные A и B. В одной функции вы сначала блокируете A, а затем блокируете B. В другой функции вы начинаете блокировать B, а через некоторое время блокируете A. Это является потенциальной тупиковой ситуацией, когда первая функция будет ожидать разблокировки B, а вторая функция будет ожидать разблокировки A. Эта проблема, вероятно, не будет возникать в вашей среде разработки и только время от времени. Чтобы избежать этого, все замки всегда должны быть в одном и том же порядке.
Да и нет.
Безопасность потоков - это нечто большее, чем просто обеспечение доступа к вашим общим данным только одним потоком за раз. Вы должны обеспечить последовательный доступ к общим данным, избегая при этом условий гонки , взаимоблокировок , блокировок в реальном времени и нехватки ресурсов .
Непредсказуемые результаты при работе нескольких потоков не являются обязательным условием потокобезопасного кода, но часто являются побочным продуктом. Например, можно создать схему « производитель-потребитель» с общей очередью, одним потоком производителя и несколькими потоками-потребителями, и поток данных может быть совершенно предсказуемым. Если вы начнете представлять больше потребителей, вы увидите больше случайных результатов.
В сущности, в многопоточной среде может произойти много ошибок (переупорядочивание инструкций, частично построенные объекты, одна и та же переменная, имеющая разные значения в разных потоках из-за кэширования на уровне ЦП и т. Д.).
Мне нравится определение, данное Java Concurrency на практике :
[Часть кода] является поточно-ориентированной, если она ведет себя правильно при доступе из нескольких потоков, независимо от планирования или чередования выполнения этих потоков средой выполнения, и без дополнительной синхронизации или другой координации со стороны код вызова.
Под правильными они подразумевают, что программа ведет себя в соответствии со своими спецификациями.
Придуманный пример
Представьте, что вы реализуете счетчик. Вы можете сказать, что он ведет себя правильно, если:
counter.next()
никогда не возвращает значение, которое уже было возвращено ранее (для простоты мы не предполагаем переполнения и т. д.)Потокобезопасный счетчик будет вести себя в соответствии с этими правилами независимо от того, сколько потоков обращается к нему одновременно (что, как правило, не относится к наивной реализации).
Примечание: кросс-пост на программистов
Проще говоря, код будет работать нормально, если многие потоки исполняют этот код одновременно.
Не путайте безопасность потоков с детерминизмом. Потокобезопасный код также может быть недетерминированным. Учитывая сложность отладки проблем с многопоточным кодом, это, вероятно, нормальный случай. :-)
Безопасность потока просто гарантирует, что когда поток изменяет или читает совместно используемые данные, никакой другой поток не может получить к нему доступ таким образом, чтобы изменить данные. Если ваш код зависит от определенного порядка выполнения для правильности, то вам нужны другие механизмы синхронизации, помимо тех, которые требуются для безопасности потоков, чтобы обеспечить это.
Я хотел бы добавить больше информации к другим хорошим ответам.
Безопасность потоков подразумевает, что несколько потоков могут записывать / считывать данные в одном и том же объекте без ошибок несогласованности памяти. В многопоточных программах многопоточная программа не вызывает побочных эффектов для общих данных .
Посмотрите на этот вопрос 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()
в каждом потоке, а не до соответствующих операций exchange () в другом потоке.CyclicBarrier.await
и Phaser.awaitAdvance
(а также его варианты) выполняются до действий, выполняемых барьерным действием, и действия, выполняемые барьерным действием, выполняются до действий, следующих за успешным возвратом из соответствующего ожидающего в других потоках.Чтобы завершить другие ответы:
Синхронизация вызывает беспокойство только тогда, когда код в вашем методе выполняет одно из двух:
Это означает, что переменные, определенные в вашем методе, всегда являются потокобезопасными. Каждый вызов метода имеет свою версию этих переменных. Если метод вызывается другим потоком или тем же потоком, или даже если метод вызывает сам себя (рекурсия), значения этих переменных не разделяются.
Планирование потоков не гарантируется для циклического перебора . Задача может полностью перегружать процессор за счет потоков с одинаковым приоритетом. Вы можете использовать Thread.yield (), чтобы иметь совесть. Вы можете использовать (в Java) Thread.setPriority (Thread.NORM_PRIORITY-1), чтобы понизить приоритет потока
Плюс остерегайтесь:
Да и да. Это означает, что данные не изменяются более чем одним потоком одновременно. Тем не менее, ваша программа может работать, как ожидалось, и выглядеть поточно-ориентированной, даже если это принципиально не так.
Обратите внимание, что непредсказуемость результатов является следствием «состояния гонки», которое, вероятно, приводит к изменению данных в порядке, отличном от ожидаемого.
Давайте ответим на это на примере:
class NonThreadSafe {
private int counter = 0;
public boolean countTo10() {
count = count + 1;
return (count == 10);
}
countTo10
Метод добавляет один к прилавку , а затем возвращает истину , если число достигло 10. Он должен возвращать только истинный раз.
Это будет работать, пока только один поток выполняет код. Если два потока запускают код одновременно, могут возникнуть различные проблемы.
Например, если count начинается с 9, один поток может добавить 1 к счету (делая 10), но затем второй поток может ввести метод и снова добавить 1 (делая 11), прежде чем первый поток сможет выполнить сравнение с 10 Затем оба потока выполняют сравнение и обнаруживают, что число равно 11, и ни один из них не возвращает значение true.
Так что этот код не является потокобезопасным.
По сути, все проблемы с многопоточностью вызваны некоторыми вариациями такого рода проблем.
Решение состоит в том, чтобы гарантировать, что сложение и сравнение не могут быть разделены (например, путем окружения двух операторов каким-либо кодом синхронизации) или путем разработки решения, которое не требует двух операций. Такой код будет потокобезопасным.
По крайней мере, в C ++ я думаю о поточно-ориентированном, как о неправильном значении в том смысле, что он многое исключает из названия. Чтобы быть потокобезопасным, код обычно должен быть проактивен в этом отношении. Это вообще не пассивное качество.
Чтобы класс был безопасен для протектора, он должен иметь «дополнительные» функции, которые увеличивают накладные расходы. Эти функции являются частью реализации класса и, вообще говоря, скрыты от интерфейса. То есть разные потоки могут обращаться к любому члену класса, не беспокоясь о конфликте с параллельным доступом другого потока, и могут делать это очень лениво, используя обычный старый обычный стиль кодирования человека, без необходимости делать все эти сумасшедшие вещи синхронизации, которые уже заложены в вызываемый код.
И именно поэтому некоторые люди предпочитают использовать термин внутренне синхронизированный .
Есть три основных набора терминов для этих идей, с которыми я столкнулся. Первое и исторически более популярное (но хуже) это:
Второе (и лучшее) это:
Третье это:
поточно ~ нить доказательство ~ внутренне синхронизированный
Примером системы с внутренней синхронизацией (иначе говоря, поточно-безопасной или поточно-защищенной ) является ресторан, в котором хост приветствует вас у двери и запрещает вам ставить себя в очередь. Хозяин является частью механизма ресторана для работы с несколькими клиентами, и может использовать некоторые довольно хитрые приемы для оптимизации рассадки ожидающих клиентов, например, принимая во внимание размер их группы или время, которое они выглядят, как у них есть. или даже принимая бронирование по телефону. Ресторан внутренне синхронизирован, потому что все это является частью интерфейса для взаимодействия с ним.
не поточно-безопасный (но приятный) ~ совместимый с потоками ~ внешне синхронизированный ~ свободный поток
Предположим, что вы идете в банк. Существует линия, то есть раздор для банковских кассиров. Поскольку вы не дикарь, вы признаете, что лучшее, что нужно сделать в разгар борьбы за ресурс, - это стоять в очереди, как цивилизованное существо. Никто технически не заставляет вас делать это. Мы надеемся, что у вас есть необходимые социальные программы, чтобы сделать это самостоятельно. В этом смысле банковское лобби внешне синхронизировано. Должны ли мы сказать, что это небезопасно? это то, что подразумевается, если вы идете с потокобезопасным , небезопасным биполярным терминологическим набором. Это не очень хороший набор терминов. Лучшая терминология внешне синхронизирована,Банковское лобби не является враждебным к тому, чтобы к нему обращались несколько клиентов, но оно также не выполняет их синхронизацию. Клиенты делают это сами.
Это также называется «свободной резьбой», где «свободной» является то же, что и «свободной от вшей» - или в этом случае блокировками. Ну, точнее, примитивы синхронизации. Это не означает, что код может работать в нескольких потоках без этих примитивов. Это просто означает, что он не поставляется с уже установленными, и вы, пользователь кода, можете установить их самостоятельно, как считаете нужным. Установка собственных примитивов синхронизации может быть сложной и требует тщательного обдумывания кода, но также может привести к созданию самой быстрой из возможных программ, позволяя вам настроить способ выполнения программы на современных многопоточных ЦП.
не потокобезопасный (и плохой) ~ враждебный поток ~ несинхронизируемый
Пример повседневной аналогии с враждебной системой - какой-то придурок со спортивным автомобилем, отказывающийся использовать свои поворотники и изменяющий полосу движения. Их стиль вождения является враждебным или несинхронизируемым, потому что у вас нет возможности координировать действия с ними, и это может привести к конфликту на одной полосе без разрешения и, как следствие, к аварии, когда две машины попытаются занять одно и то же пространство без какого-либо протокола. предотвратить это. Этот шаблон также можно рассматривать как более широкую антиобщественную, что я предпочитаю, потому что он менее специфичен для потоков и поэтому в целом применим ко многим областям программирования.
Первый и самый старый набор терминов не удается сделать более тонкое различие между нитью враждебностью и совместимостью потоков . Совместимость потоков более пассивна, чем так называемая безопасность потоков, но это не означает, что вызываемый код небезопасен для одновременного использования потоков. Это просто означает, что это пассивно в отношении синхронизации, которая позволила бы это, откладывая ее в вызывающий код, вместо того, чтобы предоставлять ее как часть его внутренней реализации. Совместимость с потоками - это то, как код, вероятно, должен быть написан по умолчанию в большинстве случаев, но это также, к сожалению, часто ошибочно считается небезопасным потоком, как будто это по своей сути анти-безопасность, что является основной причиной путаницы для программистов.
ПРИМЕЧАНИЕ. Во многих руководствах по программному обеспечению термин «потокобезопасный» фактически используется для обозначения «совместимого с потоками», что добавляет еще больше путаницы к тому, что уже было беспорядком! Я избегаю терминов «потокобезопасный» и «поток-небезопасный» любой ценой по этой самой причине, поскольку некоторые источники будут называть что-то «потокобезопасным», в то время как другие будут называть это «поток-небезопасным», потому что они не могут согласиться о том, нужно ли вам соблюдать некоторые дополнительные стандарты безопасности (примитивы синхронизации), или просто НЕ быть враждебным, чтобы считаться «безопасным». Поэтому избегайте этих терминов и используйте вместо них более умные, чтобы избежать опасного недопонимания с другими инженерами.
По сути, наша цель - разрушить хаос.
Мы делаем это, создавая детерминированные системы, на которые можно положиться. Детерминизм стоит дорого, в основном из-за альтернативных издержек потери параллелизма, конвейеризации и переупорядочения. Мы стараемся минимизировать количество детерминизма, в котором мы нуждаемся, чтобы удерживать наши расходы на низком уровне, а также избегать принятия решений, которые еще больше подорвут тот маленький детерминизм, который мы можем себе позволить.
Синхронизация потоков заключается в увеличении порядка и уменьшении хаоса. Уровни, на которых вы делаете это, соответствуют условиям, упомянутым выше. Самый высокий уровень означает, что система ведет себя абсолютно предсказуемым образом каждый раз. Второй уровень означает, что система ведет себя достаточно хорошо, чтобы вызывающий код мог надежно обнаружить непредсказуемость. Например, ложное пробуждение в условной переменной или сбой блокировки мьютекса, потому что он не готов. Третий уровень означает, что система не ведет себя достаточно хорошо, чтобы играть с кем-либо еще, и может работать только когда-либо однопоточно, не вызывая хаоса.
Вместо того, чтобы думать о коде или классах как о поточно-безопасных или нет, я думаю, что более полезно думать о действиях как о поточно-безопасных. Два действия являются потокобезопасными, если они будут вести себя так, как указано при запуске из произвольного контекста потоков. Во многих случаях классы будут поддерживать некоторые комбинации действий потокобезопасным способом, а другие - нет.
Например, многие коллекции, такие как списки массивов и наборы хэшей, гарантируют, что если к ним изначально обращаются исключительно с одним потоком, и они никогда не изменяются после того, как ссылка становится видимой для любых других потоков, они могут быть прочитаны произвольным образом любой комбинацией нитей без помех.
Более интересно то, что некоторые коллекции хэш-наборов, такие как исходная неуниверсальная коллекция в .NET, могут предложить гарантию того, что до тех пор, пока ни один элемент не будет удален, и при условии, что в них записывается только один поток, любой поток, который пытается чтение коллекции будет вести себя так, как если бы она обращалась к коллекции, где обновления могут задерживаться и происходить в произвольном порядке, но в противном случае они будут вести себя нормально. Если поток № 1 добавляет X, а затем Y, а поток № 2 ищет и видит Y, а затем X, для потока № 2 было бы возможно увидеть, что Y существует, но X нет; будет ли такое поведение «поточно-ориентированным», будет зависеть от того, готов ли поток №2 справиться с такой возможностью.
В заключение, некоторые классы, особенно блокирующие коммуникационные библиотеки, могут иметь метод «close» или «Dispose», который является поточно-ориентированным по отношению ко всем другим методам, но не имеет других методов, которые являются поточно-ориентированными по отношению к друг с другом. Если поток выполняет блокирующий запрос на чтение, и пользователь программы нажимает «отменить», то поток, который пытается выполнить чтение, не сможет выдать закрытый запрос. Однако запрос на закрытие / удаление может асинхронно установить флаг, который приведет к отмене запроса на чтение как можно скорее. Как только закрытие будет выполнено в любом потоке, объект станет бесполезным, и все попытки будущих действий немедленно потерпят неудачу,