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

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

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

Обновление: Будут ли я сталкиваться с серьезными проблемами, если я проверю

Controls.CheckForIllegalCrossThread..blah =true

Ответы

Ответ 1

Эрик Липперт имеет хорошее сообщение в блоге под названием Что это за вещь, которую вы называете "потокобезопасной" ? об определении безопасности потоков, найденных в Википедии.

3 важные вещи, извлеченные из ссылок:

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

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

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

Определенно стоит прочитать!

Ответ 2

В простейшем из терминов threadsafe означает, что безопасно получать доступ из нескольких потоков. Когда вы используете несколько потоков в программе, и каждый из них пытается получить доступ к общей структуре данных или местоположению в памяти, может произойти несколько плохих вещей. Таким образом, вы добавляете дополнительный код, чтобы предотвратить эти плохие вещи. Например, если два человека одновременно писали один и тот же документ, второй человек, который будет сохранен, перезапишет работу первого человека. Чтобы сделать его потокобезопасным, вы должны заставить человека 2 ждать, пока человек 1 выполнит свою задачу, прежде чем разрешить человеку 2 редактировать документ.

Ответ 3

Wikipedia содержит статью о безопасности потоков.

Эта страница (вы должны пропустить объявление - извините) определяет ее таким образом:

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

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

Ответ 4

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

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

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

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

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

Ответ 5

Просто потокобезопасность означает, что экземпляр метода или класса может использоваться несколькими потоками одновременно без каких-либо проблем.

Рассмотрим следующий метод:

private int myInt = 0;
public int AddOne()
{
    int tmp = myInt;
    tmp = tmp + 1;
    myInt = tmp;
    return tmp;
}

Теперь поток A и поток B оба хотели бы выполнить AddOne(). но A начинает сначала и считывает значение myInt (0) в tmp. Теперь по какой-то причине планировщик решает остановить поток A и отложить выполнение до потока B. В настоящее время поток B также считывает значение myInt (все еще 0) в свою собственную переменную tmp. Thread B завершает весь метод, поэтому в конце myInt = 1. И возвращается 1. Теперь это Thread A снова повернется. Тема А продолжается. И добавляет 1 к tmp (tmp был 0 для потока A). И затем сохраняет это значение в myInt. myInt снова 1.

Итак, в этом случае метод AddOne был вызван два раза, но поскольку метод не был реализован поточно-безопасным способом, значение myInt не равно 2, как и ожидалось, но 1, потому что второй поток прочитал переменную myInt перед первый поток завершил обновление.

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

Ответ 6

Вы можете получить больше объяснений из книги "Java Concurrency in Practice":

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

Ответ 7

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

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

Блокировать объекты поддерживают блокировки идиом, которые упрощают многие параллельные приложения.

Исполнители определяют API высокого уровня для запуска и управления потоками. Реализации Executor, предоставленные java.util.concurrent, обеспечивают управление пулами потоков, подходящее для крупномасштабных приложений.

Concurrent Collections упрощают управление большими наборами данных и могут значительно уменьшить необходимость синхронизации.

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

ThreadLocalRandom (в JDK 7) обеспечивает эффективное генерирование псевдослучайных чисел из нескольких потоков.

Обратитесь к java.util.concurrent и java.util.concurrent.atomic для других программных конструкций.

Ответ 8

В реальном мире пример для непрофессионала

Предположим, у вас есть банковский счет в Интернете и мобильном банке, а у вашей учетной записи всего 10 $. Вы выполнили перенос баланса на другой счет с помощью мобильного банкинга, а между тем вы делали онлайн-покупки с использованием одного и того же банковского счета. Если этот BankAccount не "THREAD SAFE", то банк разрешит вам выполнить две транзакции, а затем банк станет банкротом.

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

Ответ 9

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

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

Ответ 10

Я нахожу понятие http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 тем, что, как я обычно думаю, является небезопасной потоковой обработкой, когда метод имеет и полагается на побочный эффект, такой как глобальная переменная.

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

//built in global set to locale specific value (here a comma)
decimalSeparator = ','

function FormatDot(value : real):
    //save the current decimal character
    temp = decimalSeparator

    //set the global value to be 
    decimalSeparator = '.'

    //format() uses decimalSeparator behind the scenes
    result = format(value)

    //Put the original value back
    decimalSeparator = temp

Ответ 11

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

4.3.1. Пример: Отслеживание транспортных средств с помощью делегирования

В качестве более существенного примера делегирования, позвольте построить версию трекера транспортного средства, который делегирует класс, защищенный потоками. Мы сохраняем местоположения на карте, поэтому мы начинаем с реалистичной реализации Map, ConcurrentHashMap. Мы также сохраняем местоположение с использованием неизменяемого класса Point вместо MutablePoint, показанного в листинге 4.6.

Листинг 4.6. Класс Nonutable Point, используемый DelegatingVehicleTracker.

 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

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

DelegatingVehicleTracker в листинге 4.7 не используется явная синхронизация; все доступ к состоянию управляется ConcurrentHashMap, и все ключи и значения Карты неизменяемы.

Листинг 4.7. Передача безопасности потока на ConcurrentHashMap.

  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }

}

Если бы мы использовали исходный класс MutablePoint вместо Point, мы бы разрывали инкапсуляцию, позволяя getLocations публиковать ссылку на изменяемое состояние, которое не является потокобезопасным. Обратите внимание, что мы немного изменили поведение класса трекера транспортного средства; в то время как версия монитора вернула моментальный снимок местоположений, делегирующая версия возвращает немодифицируемое, но "живое" представление о местонахождении автомобиля. Это означает, что если поток A вызывает getLocations, а поток B позже изменяет местоположение некоторых точек, эти изменения отражаются в карте, возвращенной в поток A.

4.3.2. Независимые переменные состояния

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

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

Листинг 4.9. Передача безопасности потока в несколько переменных базового состояния.

public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}

VisualComponent использует CopyOnWriteArrayList для хранения каждого списка слушателей; это поточно-безопасная реализация списка, особенно подходящая для управления списками слушателей (см. раздел 5.2.3). Каждый Список является потокобезопасным, и поскольку нет ограничений, связывающих состояние одного с состоянием другого, VisualComponent может делегировать свои обязанности по обеспечению безопасности потока к базовым объектам mouseListeners и keyListeners.

4.3.3. Когда делегирование завершается

Большинство составных классов не так просты, как VisualComponent: они имеют инварианты, которые связывают переменные состояния компонента. NumberRange в листинге 4.10 использует два AtomicIntegers для управления своим состоянием, но накладывает дополнительное ограничение - первое число меньше или равно второму.

Листинг 4.10. Класс диапазона, который недостаточно защищает его инварианты. Не делайте этого.

public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}

NumberRange не является потокобезопасным; он не сохраняет инвариант, который ограничивает нижний и верхний. Методы setLower и setUpper пытаются оценить этот инвариант, но делают это плохо. Оба setLower и setUpper являются последовательностями последовательности проверки, но они не используют достаточную блокировку, чтобы сделать их атомарными. Если диапазон номеров удерживается (0, 10), а один поток вызывает setLower(5), а другой поток вызывает setUpper(4), с некоторым неудачным временем оба будут проходить проверки в сеттерах, и будут применены обе модификации. В результате диапазон теперь удерживает (5, 4) - недопустимое состояние. Таким образом, , в то время как базовые AtomicIntegers являются потокобезопасными, составной класс не. Поскольку основные переменные состояния lower и upper не являются независимыми, NumberRange не может просто делегировать безопасность потока в свои переменные состояния, зависящие от потока.

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

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

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