Безопасность потоков - Thread safety

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

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

Уровни безопасности потоков

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

Разные производители используют немного разную терминологию для обеспечения безопасности потоков:[3][4][5][6]

  • Потокобезопасный: Реализация гарантированно свободна от условия гонки при одновременном доступе нескольких потоков.
  • Условно безопасно: Разные потоки могут обращаться к разным объектам одновременно, а доступ к общим данным защищен от состояния гонки.
  • Не потокобезопасный: К структурам данных нельзя обращаться одновременно для разных потоков.

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

Подходы к реализации

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

Первый класс подходов направлен на предотвращение совместного использования состояния и включает:

Повторный вход
Написание кода таким образом, чтобы он мог частично выполняться потоком, повторно выполняться тем же потоком или одновременно выполняться другим потоком и при этом правильно завершать исходное выполнение. Это требует экономии штат информация в переменных, локальных для каждого выполнения, обычно в стеке, а не в статический или Глобальный переменные или другое нелокальное состояние. Все нелокальные состояния должны быть доступны через атомарные операции, и структуры данных также должны быть реентерабельными.
Локальное хранилище потока
Переменные локализованы, поэтому каждый поток имеет свою собственную частную копию. Эти переменные сохраняют свои значения через подпрограмма и другие границы кода, и являются потокобезопасными, поскольку они локальны для каждого потока, даже если код, который обращается к ним, может выполняться одновременно другим потоком.
Неизменяемые объекты
Состояние объекта нельзя изменить после постройки. Это означает, что совместно используются только данные только для чтения и обеспечивается внутренняя безопасность потоков. Изменяемые (неконстантные) операции могут быть реализованы таким образом, что они создают новые объекты вместо изменения существующих. Такой подход характерен для функциональное программирование а также используется строка реализации на Java, C # и Python. (Увидеть Неизменяемый объект.)

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

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

Примеры

В следующем фрагменте Ява код, ключевое слово Java синхронизированный делает метод поточно-ориентированным:

класс Счетчик {    частный int я = 0;    общественный синхронизированный пустота inc() {        я++;    }}

в Язык программирования C, каждый поток имеет свой стек. Однако статическая переменная не хранится в стеке; все потоки имеют к нему одновременный доступ. Если несколько потоков перекрываются при выполнении одной и той же функции, возможно, что статическая переменная может быть изменена одним потоком, в то время как другой находится на полпути к ее проверке. Этот трудно диагностируемый логическая ошибка, который большую часть времени может компилироваться и работать правильно, называется состояние гонки. Один из распространенных способов избежать этого - использовать другую общую переменную в качестве «блокировка» или «мьютекс» (от мутуал бывшийclusion).

В следующем фрагменте кода C функция является поточно-ориентированной, но не реентерабельной:

# включить int increment_counter (){  статический int счетчик = 0;  статический pthread_mutex_t мьютекс = PTHREAD_MUTEX_INITIALIZER;  // разрешаем только одному потоку увеличиваться за раз  pthread_mutex_lock(&мьютекс);  ++счетчик;  // сохраняем значение перед тем, как другие потоки увеличат его дальше  int результат = счетчик;  pthread_mutex_unlock(&мьютекс);  вернуть результат;}

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

Одна и та же функция может быть реализована как для потоковой, так и для повторной входимости с использованием lock-free атомика в C ++ 11:

# включить <атомный>int increment_counter (){  статический стандартное::атомный<int> счетчик(0);  // приращение гарантированно выполняется атомарно  int результат = ++счетчик;  вернуть результат;}

Смотрите также

использованная литература

  1. ^ Керриск, Майкл (2010). Интерфейс программирования Linux. Пресс без крахмала. п. 655.
  2. ^ «Руководство по многопоточному программированию». Корпорация Oracle. Ноябрь 2010 г. Процедура является потокобезопасной, если процедура логически корректна при одновременном выполнении несколькими потоками.
  3. ^ "Реентерабельность и безопасность потоков | Qt 5.6". Qt Project. Получено 2016-04-20.
  4. ^ "ip :: tcp - 1.51.0". Boost.org. Получено 2013-10-16.
  5. ^ «Классификация безопасности потоков API». Publib.boulder.ibm.com. 1998-06-09. Получено 2013-10-16.
  6. ^ «Уровни безопасности интерфейса MT - Руководство по многопоточному программированию». Docs.oracle.com. 2010-11-01. Получено 2013-10-16.

внешние ссылки