Що робить метод безпечним для ниток? Які правила?


156

Чи існують загальні правила / рекомендації щодо того, що робить метод безпечним для потоків? Я розумію, що, мабуть, мільйон разових ситуацій, а як взагалі? Це просто?

  1. Якщо метод має доступ лише до локальних змінних, це безпечно для потоків.

Це все? Чи застосовується це і для статичних методів?

Одна відповідь, надана @Cybis:

Локальні змінні не можна ділити між потоками, оскільки кожен потік отримує власний стек.

Це стосується і статичних методів?

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

Отже, я думаю, моє остаточне запитання: "Чи є короткий перелік правил, які визначають безпечний для потоків метод? Якщо так, то які вони?"

EDIT
Тут було зроблено багато хороших моментів. Я думаю, що справжньою відповіддю на це питання є: "Немає простих правил для забезпечення безпеки потоку". Класно. Чудово. Але загалом я думаю, що прийнята відповідь дає хороший, короткий підсумок. Завжди є винятки. Нехай буде так. Я можу з цим жити.


59
Ви не маєте доступу до змінних, до яких також звертаються інші потоки без блокування.
Ганс Пасант

4
Пантант Ханта перетворився на ігора!
Мартін Джеймс

3
Також .. "Ви не маєте доступу до змінних, до яких також можна отримати доступ до інших потоків без замкнення", - це викреслює значення, якщо значення, яке зчитується, іноді не є останнім або насправді є неправильним.
Мартін Джеймс

Ось приємний блог Еріка, щоб перенести вас у вихор.
RBT

Відповіді:


139

Якщо метод (екземпляр або статичний) посилається лише на змінні, на які поширюється цей метод, то він є безпечним для потоків, оскільки кожен потік має власний стек:

У цьому випадку кілька потоків можуть дзвонити ThreadSafeMethodодночасно без проблем.

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

Це також справедливо, якщо метод викликає інший метод класу, який посилається лише на локально змінені змінні:

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

Якщо метод отримує доступ до будь-яких властивостей або полів (стан об'єкта) (екземпляр або статичний), вам потрібно використовувати блоки, щоб переконатися, що значення не змінюються іншим потоком.

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

Ви повинні знати, що будь-які параметри, передані методу, які не є ні структурою, ні незмінними, можуть бути мутовані іншим потоком поза межами методу.

Для забезпечення належної одночасності вам потрібно застосувати блокування.

Додаткову інформацію див. у записі блокування C # reference та ReadWriterLockSlim .

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


15
У третьому прикладі private string someValue;це не staticтак, що кожен екземпляр отримає окрему копію цієї змінної. Тож чи можете ви пояснити, як це не безпечно для потоків?
Bharadwaj

29
@Bharadwaj, якщо є один екземпляр Thingкласу, до якого звертаються декілька потоків
Trevor Pilley,

111

Якщо метод має доступ лише до локальних змінних, це безпечно для потоків. Це все?

Абсолютно ні. Ви можете написати програму лише з однією локальною змінною, доступ до якої є з одного потоку, який, тим не менш, не є безпечним для потоків:

https://stackoverflow.com/a/8883117/88656

Чи застосовується це і для статичних методів?

Абсолютно не.

Одна відповідь, надана @Cybis, була: "Локальні змінні не можна ділити між потоками, оскільки кожен потік отримує власний стек."

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

Це стосується і статичних методів?

Абсолютно не.

Якщо метод передається посилальним об'єктом, чи порушує це безпека потоку?

Може бути.

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

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

Отже, я думаю, моє остаточне запитання таке: "Чи є короткий список правил, які визначають безпечний для потоків метод?

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

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

Ще раз погляньте на мій приклад: кожен метод тривіальний . Саме спосіб взаємодії один з одним на "глобальному" рівні робить програму тупиковою. Ви не можете розглядати кожен метод і перевіряти його як "безпечний", а потім очікувати, що вся програма є безпечною, і не більше, ніж ви можете зробити висновок, що оскільки ваш будинок виготовлений з 100% не порожнистих цегли, що і будинок не порожнистий. Порожнеча будинку - це глобальна властивість всієї речі, а не сукупність властивостей її частин.


14
Основне твердження: Безпека ниток - це глобальна, а не локальна властивість програми.
Грег Д

5
@BobHorn: class C { public static Func<int> getter; public static Action<int> setter; public static void M() { int x = 0; getter = ()=>x; setter = y=>{x=y;};} } Зателефонуйте M (), потім викличте C.getter та C.setter у двох різних потоках. Локальна змінна тепер може бути записана і прочитана з двох різних потоків, навіть якщо вона є локальною. Знову ж таки: визначальною характеристикою локальної змінної є те, що вона локальна , а не те, що вона знаходиться в стеці потоку .
Ерік Ліпперт

3
@BobHorn: Я думаю, що це пов'язане з розумінням наших інструментів на такому рівні, що ми можемо говорити про них авторитетом та знаннями. Наприклад, оригінальне запитання зрадило нерозуміння того, що таке локальна змінна. Ця помилка є досить поширеною, але факт полягає в тому, що вона є помилкою і її слід виправити. Визначення локальної змінної досить точне і до неї слід ставитися відповідно. :) Це не відповідь для "людей", які не розуміють, що патологічні випадки існують. Це уточнення, щоб ви могли зрозуміти, що ви насправді запитали. :)
Грег Д

7
@EricLippert: Документація MSDN для багатьох класів заявляє, що «Публічні статичні (Спільні в Visual Basic) члени цього типу є безпечними для потоків. Будь-які члени екземплярів не гарантують безпеку потоку. " або іноді "Цей тип захищений від потоку". (наприклад, String), як можна зробити ці гарантії, коли безпека ниток є глобальною проблемою?
Майк Зборай

3
@mikez: Добре запитання. Або ці методи не мутують жоден стан, або коли вони роблять, вони мітують стан безпечно без блокування, або, якщо вони використовують блокування, вони роблять це таким чином, що гарантує, що глобальний "порядок блокування" не буде порушений. Струни - це незмінні структури даних, методи яких нічого не мутують, тому рядок можна легко зробити безпечним.
Ерік Ліпперт

11

Не існує жорсткого і швидкого правила.

Ось кілька правил, щоб зробити потік коду безпечним у .NET, і чому це не хороші правила:

  1. Функція та всі функції, які вона викликає, повинні бути чистими (без побічних ефектів) та використовувати локальні змінні. Хоча це зробить ваш код безпечним для потоку коду, також є дуже мало цікавого, що ви можете зробити з цим обмеженням у .NET.
  2. Кожна функція, яка діє на спільному об'єкті, повинна бути lockнад загальною річчю. Всі замки потрібно робити в одному порядку. Це зробить потік коду безпечним, але він буде неймовірно повільним, і ви можете також не використовувати декілька потоків.
  3. ...

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


10
Замки не повинні бути повільними. Замки неймовірно швидкі; безперервний замок знаходиться в порядку величини від десяти до ста наносекунд . Оспорювані замки, звичайно, довільно повільні; якщо ви сповільнені через те, що ви оскаржуєте блокування, перейдіть до програми, щоб видалити суперечку .
Ерік Ліпперт

2
Напевно, я не зміг зробити №2 досить зрозумілим. Я намагався сказати, що є спосіб зробити нитки безпечними: поставити замки навколо кожного доступу до будь-яких загальних об'єктів. І якщо припустити, що існує декілька потоків, це породжує суперечки, і блокування сповільнить всю справу. При додаванні замків не можна просто сліпо додавати блоки, вони повинні розміщуватися в дуже хороших стратегічних місцях.
EarlNameless

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