Что значит ориентирована на многопотоковое исполнение?


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

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

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

Controls.CheckForIllegalCrossThread..blah =true
11 92

11 ответов:

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

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

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

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

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

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

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

Википедия есть статья о потокобезопасности.

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

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

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

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

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

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

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

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

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

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

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. Поток B завершает весь метод, поэтому в конце myInt = 1. И 1 возвращается. Теперь снова очередь нити А. Нить а продолжается. И добавляет 1 к tmp (tmp был 0 для потока A). А затем сохраняет это значение в myInt. myInt снова 1.

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

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

вы можете получить больше объяснений из книги "параллелизм Java на практике":

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

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

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

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

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

Параллельные Коллекции упрощения управления большими коллекциями данных и может значительно уменьшить потребность в синхронизации.

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

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

смотрите java.утиль.одновременно и java.утиль.параллельный.атомный пакеты для других программных конструкций.

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

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

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

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

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

Я нахожу понятие 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

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

4.3.1. Пример: Трекер Транспортного Средства С Использованием Делегирования

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

в листинге 4.6. Неизменяемый класс точки, используемый 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 класс вместо точки, мы будем нарушать инкапсуляцию, позволяя getLocations опубликовать ссылку на изменяемое состояние, которое не является потокобезопасным. Обратите внимание, что мы немного изменили поведение класса Vehicle tracker; в то время как версия монитора вернула снимок из местоположений делегирующая версия возвращает неизменяемый, но" живой " вид местоположений транспортного средства. Это означает, что если поток 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 являются последовательностями check-then-act, но они не делают используйте достаточную блокировку, чтобы сделать их атомарными. Если диапазон чисел содержит (0, 10), и один поток вызывает setLower(5) в то время как другой поток вызывает setUpper(4), С некоторым неудачным временем оба пройдут проверки в сеттерах, и будут применены обе модификации. В результате диапазон теперь имеет значение (5, 4) -недопустимом состоянии. Так что в то время как базовые AtomicIntegers являются потокобезопасными, составной класс не является. Потому что базовые переменные состояния lower и upper не являются независимыми, NumberRange нельзя просто делегировать потокобезопасность своим потокобезопасным переменным состояния.

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

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

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