Що означає безпека ниток?


124

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

Але навіть я не зовсім зрозумів, чому потрібен весь додатковий код.

Оновлення: чи зіткнуться я з будь-якими серйозними проблемами, якщо перевіряю

Controls.CheckForIllegalCrossThread..blah =true

5
Як правило, "безпечна нитка" означає те, що людина, що використовує термін, думає, що це означає, принаймні для цієї людини. Таким чином, це не дуже корисна мовна конструкція - вам потрібно бути набагато, набагато конкретнішим, коли говорити про поведінку потокового коду.


@dave Вибачте, що я спробував шукати, але відмовився ... все одно дякую ..
Vivek Bernard

1
код, який не виникаєRace-Condition
Мухаммед Бабар

Відповіді:


121

У Еріка Ліпперта є приємна публікація в блозі під назвою " Що це за те, що ви називаєте" безпечним потоком "? про визначення безпеки потоку, як знайдено у Вікіпедії.

3 важливі речі, витягнуті з посилань:

"Шматок коду є безпечним для потоків, якщо він працює правильно під час одночасного виконання декількох потоків."

"Зокрема, він повинен задовольняти потребу в декількох потоках для доступу до одних і тих же спільних даних ..."

"... і необхідність доступу до спільного фрагмента даних лише одним потоком в будь-який момент".

Однозначно варто прочитати!


25
Будь ласка, уникайте лише відповідей на посилання, оскільки це може стати поганим у будь-який час у майбутньому.
akhil_mittal


106

Найпростішими термінами, безпечний потік означає, що це безпечний доступ з декількох потоків. Коли ви використовуєте декілька потоків у програмі, і кожен з них намагається отримати доступ до загальної структури даних або місця в пам'яті, може статися декілька поганих речей. Отже, ви додаєте додатковий код, щоб запобігти цим поганим речам. Наприклад, якщо двоє людей одночасно писали один і той же документ, друга особа, яку потрібно зберегти, замінить роботу першої особи. Щоб зробити це потоком безпечним, вам доведеться змусити людину 2 чекати, коли особа 1 виконає своє завдання, перш ніж дозволити особі 2 редагувати документ.


11
Це називається синхронізацією. Правильно?
JavaTechNI

3
Так. Примушування різних потоків чекати доступу до спільного ресурсу може бути виконано за допомогою синхронізації.
Вінсент Рамдані

З прийнятої відповіді Грегорі він говорить: "Частинка коду є безпечною для потоків, якщо вона функціонує правильно під час одночасного виконання кількох потоків". в той час як ви говорите , « Для того, щоб зробити його поточно то, ви повинні змусити людину 1 чекати», це він не говорить одночасне є прийнятним в той час як ви говорите , що це не могли б ви пояснити?
Мед

Це те саме. Я просто пропоную простий механізм як приклад того, що робить нитку коду безпечною. незалежно від використовуваного механізму, хоча кілька потоків, що працюють з одним і тим же кодом, не повинні перешкоджати один одному.
Вінсент Рамдані

Так це стосується лише коду, що використовує глобальні та статичні змінні? Використовуючи ваш приклад людей, що редагують документи, я вважаю, що не має сенсу заважати особі 2 запускати код написання документа на інший документ.
Аарон Франке

18

Вікіпедія є стаття про безпеку ниток.

Ця сторінка визначень (вам потрібно пропустити оголошення - вибачте) визначає її таким чином:

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

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

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

Оскільки цим потокам часто потрібно буде виконувати загальні дії - введення / виведення диска, виведення результатів на екран тощо. - Ці частини коду потрібно буде записати таким чином, щоб вони могли обробляти виклики з декількох потоків, часто на одночасно. Це стосуватиметься таких речей, як:

  • Робота над копіями даних
  • Додавання замків навколо критичного коду

8

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

Розглянемо наступний метод:

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 ви можете позначити метод як синхронізований, це означає, що лише один потік може виконати цей метод в даний момент часу. Інші нитки чекають в черзі. Це робить потік методу безпечним, але якщо в роботі необхідно зробити багато роботи, то це витрачає багато місця. Інша техніка - це "позначити лише невелику частину методу як синхронізовану"створивши замок або семафор і зафіксувавши цю невелику частину (зазвичай її називають критичною секцією). Існують навіть деякі методи, які реалізовані як безпечні для потоку, що не захищаються, а це означає, що вони побудовані таким чином, що декілька потоків можуть перебігати через них одночасно, не завдаючи жодних проблем. Це може бути, коли метод лише виконує один атомний виклик. Атомні дзвінки - це дзвінки, які неможливо перервати і можуть виконуватись лише одним потоком за раз.


якщо метод AddOne викликали два рази
Sujith PS

6

У реальному світі прикладом для мирян є

Припустимо, у вас є банківський рахунок в Інтернеті та мобільному банкінгу, а ваш рахунок лише 10 доларів. Ви здійснили переказ коштів на інший рахунок за допомогою мобільного банкінгу, а тим часом здійснювали покупки в Інтернеті, використовуючи той же банківський рахунок. Якщо цей банківський рахунок не є безпечним, то банк дозволяє одночасно здійснити дві операції, і тоді банк збанкрутує.

Threadsafe означає, що стан об'єкта не змінюється, якщо одночасно кілька потоків намагаються отримати доступ до об'єкта.


5

Ви можете отримати більше пояснень із книги "Конкурс Java на практиці":

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


4

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

Тут модулем може бути структура даних, клас, об'єкт, метод / процедура або функція. В основному обширний фрагмент коду та пов'язані з ним дані.

Гарантія потенційно може бути обмежена певними середовищами, такими як специфічна архітектура процесора, але повинна застосовуватися для таких середовищ. Якщо немає чіткого розмежування середовищ, тоді, як правило, мається на увазі, що він справедливий для всіх середовищ, що код може бути скомпільований і виконаний.

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

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


3

Безпека нитки нитки: програма, захищена від потоку, захищає дані від помилок узгодженості пам'яті. У високопоточній програмі безпечна програма для потоків не викликає побічних ефектів при багаторазових операціях читання / запису з декількох потоків на одних і тих же об'єктах. Різні потоки можуть обмінюватися та змінювати дані об'єкта без помилок узгодженості.

Ви можете досягти безпеки потоку за допомогою розширеного API паралельності. Ця сторінка з документацією містить хороші конструкції програмування для досягнення безпеки потоку.

Об'єкти блокування підтримують ідіоми блокування, які спрощують багато паралельних програм.

Виконавці визначають API високого рівня для запуску і управління потоками. Реалізації виконавця, що надаються java.util.concurrent, забезпечують управління пулом потоків, що підходить для масштабних програм.

Одночасні колекції полегшують управління великими колекціями даних і можуть значно зменшити потребу в синхронізації.

Атомні змінні мають функції, що мінімізують синхронізацію та допомагають уникнути помилок узгодженості пам'яті.

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

Зверніться до пакетів java.util.concurrent і java.util.concurrent.atomic також для інших програм програмування.


1

Ви чітко працюєте в середовищі WinForms. Елементи керування WinForms демонструють спорідненість потоку, а це означає, що нитка, в якій вони створені, є єдиним потоком, який може використовуватися для доступу та оновлення. Ось чому ви знайдете приклади в MSDN та інших місцях, де продемонстровано, як повернути виклик на основний потік.

Звичайна практика WinForms - це мати єдиний потік, який присвячений всій вашій роботі інтерфейсу.


1

Я вважаю, що концепція 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

-2

Щоб зрозуміти безпеку ниток, прочитайте нижче розділи :

4.3.1. Приклад: Tracker Vehicle Tracker за допомогою делегування

Як більш вагомий приклад делегування давайте побудуємо версію трекера транспортного засобу, яка делегує до безпечного класу. Ми зберігаємо місце в карті, тому ми почнемо з реалізацією карт потокобезпечна, ConcurrentHashMap. Ми також зберігаємо місце розташування, використовуючи незмінний клас Point замість MutablePoint, показаний у Лістингу 4.6.

Лістинг 4.6. Клас Immutable 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. Делегування безпеки нитки одночасною карткою HashMap.

  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 пізніше модифікує розташування деяких точок, ці зміни відображаються в карті, поверненій до потоку А.

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використовує a 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це робиться, одне лише делегування знову не є підходящим підходом для безпеки потоку. У цих випадках клас повинен забезпечити власне блокування, щоб гарантувати, що дії сполуки є атомними, за винятком випадків, коли вся дія сполуки може бути делегована змінним базового стану.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.