Что означает термин «потокобезопасный»?

368

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

Варун Махаджан
источник
8
Только что увидел интересное обсуждение здесь по этому вопросу: blogs.msdn.com/ericlippert/archive/2009/10/19/…
Себастьян

Ответы:

257

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

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

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

Re-entrancy:

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

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

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

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

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

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

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

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

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


Blauohr
источник
4
В этой ссылке технически не хватает нескольких критических точек. Рассматриваемая общая память должна быть изменчивой (память только для чтения не может быть поточно-небезопасной), и несколько потоков должны, а) выполнять несколько операций записи в память, в течение которой память находится в несогласованном состоянии (неправильно) состояние и б) разрешить другим потокам прерывать этот поток, пока память несовместима.
Чарльз Бретана
20
при поиске в Google первым результатом является вики, нет смысла делать его здесь избыточным.
Ранвир
Что вы имеете в виду «код, обращающийся к функции»? Сама выполняемая функция - это код, не так ли?
Корай Тугай
82

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

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

Марек Блотный
источник
35
В том же процессе!
Али Афшар
Действительно, в том же процессе :)
Марек Блотный
4
«Написание кода, который будет работать стабильно в течение нескольких недель, требует крайней паранойи». Это цитата, которая мне нравится :)
Джим Т
6
Дух! этот ответ просто повторяет вопрос! --- а почему только в рамках одного и того же процесса ??? Если код завершается ошибкой, когда несколько потоков исполняют его из разных процессов, то, возможно, («общая память» может быть в файле диска), он НЕ является потокобезопасным !!
Чарльз Бретана
1
@ mg30rg. Возможно, путаница является результатом того, что кто-то думает, что когда блок кода выполняется несколькими процессами, но только одним потоком на процесс, то это все равно остается «однопоточным» сценарием, а не многопоточным. , Эта идея даже не ошибается. Это просто неверное определение. Очевидно, что несколько процессов обычно не выполняются в одном и том же потоке синхронизированным образом (за исключением редких сценариев, когда процессы по проекту координируют друг с другом и ОС разделяет потоки между процессами.)
Чарльз Бретана,
50

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

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. Четвертое и последнее условие, которое должно произойти для того, чтобы произошла гонка (и, следовательно, код НЕ должен быть «потокобезопасным»), заключается в том, что другой поток должен иметь возможность доступа к общей памяти, пока инвариант нарушен, что приводит к несогласованности или неправильное поведение
Чарльз Бретана
источник
6
Это охватывает только то, что известно как гонки данных , и, конечно, важно. Тем не менее, существуют другие способы, которыми код не может быть потокобезопасным - например, плохая блокировка, которая может привести к тупикам. Даже что-то простое, например, вызов System.exit () где-нибудь в потоке Java, делает этот код небезопасным.
Инго
2
Я предполагаю, что в некоторой степени это семантика, но я бы сказал, что плохой код блокировки, который может привести к взаимоблокировке, не делает код небезопасным. Во-первых, нет необходимости блокировать код в первую очередь, если не возможно состояние гонки, как описано выше. Затем, если вы пишете код блокировки таким образом, чтобы вызвать взаимоблокировку, это не небезопасно, это просто плохой код.
Чарльз Бретана
1
Но обратите внимание, что тупиковая ситуация не возникнет при работе в однопоточном режиме, поэтому для большинства из нас это наверняка подпадет под интуитивное значение (не) «поточно-ориентированного».
Джон Кумбс
Что ж, взаимоблокировки не могут возникнуть, если вы, конечно, не используете многопоточность, но это все равно что сказать, что проблемы с сетью не могут возникнуть, если вы работаете на одной машине. Другие проблемы также могут возникать как однопоточные, если программист пишет код так, что он выходит из критических строк кода до завершения обновления, и изменяет переменную в какой-то другой подпрограмме.
Чарльз Бретана
34

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

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

Буу Нгуен
источник
Это определение является неполным и не конкретным, и определенно не полным. Сколько раз он должен работать безопасно, только один раз? десять раз? каждый раз? 80% времени? и это не указывает, что делает его «небезопасным». Если он не может работать безопасно, но произошел сбой из-за ошибки деления на ноль, делает ли это поток "небезопасным"?
Чарльз Бретана
Будьте вежливее в следующий раз и, может быть, мы сможем обсудить Это не Reddit, и я не в настроении говорить с грубыми людьми.
Буу Нгуен
Ваши интерпретирующие комментарии о чьём-либо определении как оскорблении для вас самих говорят. Вы должны прочитать и понять сущность, прежде чем реагировать эмоционально. Ничего невежливого в моем комментарии. Я делал вывод о значении определения. Извините, если примеры, которые я использовал, чтобы проиллюстрировать это, сделали вас неудобными.
Чарльз Бретана
28

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

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

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

Маркус Даунинг
источник
22

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

Mnementh
источник
21

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

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

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

Хапкидо
источник
9

Да и нет.

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

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

Билл Ящерица
источник
9

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

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

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

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

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

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

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

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

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

assylias
источник
8

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

Грег Балаевич
источник
6

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

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

tvanfosson
источник
5

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

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

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

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

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

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

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

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

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

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

  1. Действия в потоке перед размещением объекта в любой параллельной коллекции выполняются до выполнения действий после доступа или удаления этого элемента из коллекции в другом потоке.
  2. Действия в потоке перед отправкой a 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. Для каждой пары потоков, которые успешно обмениваются объектами через Exchanger, выполняются действия до exchange()в каждом потоке, а не до соответствующих операций exchange () в другом потоке.
  6. Действия до вызова CyclicBarrier.awaitи Phaser.awaitAdvance(а также его варианты) выполняются до действий, выполняемых барьерным действием, и действия, выполняемые барьерным действием, выполняются до действий, следующих за успешным возвратом из соответствующего ожидающего в других потоках.
Равиндра Бабу
источник
4

Чтобы завершить другие ответы:

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

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

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

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

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

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

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

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

Стив Найт
источник
2

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

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.

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

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

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

rghome
источник
1

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

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

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

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

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

  1. потокобезопасный
  2. не безопасно

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

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

Третье это:

  1. внутренне синхронизированы
  2. внешне синхронизированный
  3. unsynchronizeable

Аналогии

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

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

не поточно-безопасный (но приятный) ~ совместимый с потоками ~ внешне синхронизированный ~ свободный поток

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

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

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

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

Почему потокобезопасен и др. плохой набор терминов

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

ПРИМЕЧАНИЕ. Во многих руководствах по программному обеспечению термин «потокобезопасный» фактически используется для обозначения «совместимого с потоками», что добавляет еще больше путаницы к тому, что уже было беспорядком! Я избегаю терминов «потокобезопасный» и «поток-небезопасный» любой ценой по этой самой причине, поскольку некоторые источники будут называть что-то «потокобезопасным», в то время как другие будут называть это «поток-небезопасным», потому что они не могут согласиться о том, нужно ли вам соблюдать некоторые дополнительные стандарты безопасности (примитивы синхронизации), или просто НЕ быть враждебным, чтобы считаться «безопасным». Поэтому избегайте этих терминов и используйте вместо них более умные, чтобы избежать опасного недопонимания с другими инженерами.

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

По сути, наша цель - разрушить хаос.

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

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

Дэниел Рассел
источник
1

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

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

Более интересно то, что некоторые коллекции хэш-наборов, такие как исходная неуниверсальная коллекция в .NET, могут предложить гарантию того, что до тех пор, пока ни один элемент не будет удален, и при условии, что в них записывается только один поток, любой поток, который пытается чтение коллекции будет вести себя так, как если бы она обращалась к коллекции, где обновления могут задерживаться и происходить в произвольном порядке, но в противном случае они будут вести себя нормально. Если поток № 1 добавляет X, а затем Y, а поток № 2 ищет и видит Y, а затем X, для потока № 2 было бы возможно увидеть, что Y существует, но X нет; будет ли такое поведение «поточно-ориентированным», будет зависеть от того, готов ли поток №2 справиться с такой возможностью.

В заключение, некоторые классы, особенно блокирующие коммуникационные библиотеки, могут иметь метод «close» или «Dispose», который является поточно-ориентированным по отношению ко всем другим методам, но не имеет других методов, которые являются поточно-ориентированными по отношению к друг с другом. Если поток выполняет блокирующий запрос на чтение, и пользователь программы нажимает «отменить», то поток, который пытается выполнить чтение, не сможет выдать закрытый запрос. Однако запрос на закрытие / удаление может асинхронно установить флаг, который приведет к отмене запроса на чтение как можно скорее. Как только закрытие будет выполнено в любом потоке, объект станет бесполезным, и все попытки будущих действий немедленно потерпят неудачу,

Supercat
источник
0

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

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

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

потертый
источник