Чи означає це, що два потоки не можуть одночасно змінювати базові дані? Або це означає, що даний кодовий сегмент буде працювати з передбачуваними результатами, коли цей сегмент коду виконує кілька потоків?
Чи означає це, що два потоки не можуть одночасно змінювати базові дані? Або це означає, що даний кодовий сегмент буде працювати з передбачуваними результатами, коли цей сегмент коду виконує кілька потоків?
Відповіді:
З Вікіпедії:
Безпека ниток - це концепція комп'ютерного програмування, застосовна в контексті багатопотокових програм. Шматок коду є безпечним для потоків, якщо він працює правильно під час одночасного виконання декількох потоків. Зокрема, він повинен задовольняти потребу в декількох потоках для доступу до одних і тих же спільних даних та необхідність доступу до спільного фрагмента даних лише одним потоком у будь-який момент часу.
Є кілька способів досягти безпеки ниток:
Повторне працевлаштування:
Написання коду таким чином, щоб воно могло бути частково виконане одним завданням, повторно введене іншим завданням, а потім відновлене з початкового завдання. Це вимагає збереження інформації про стан у змінних, локальних для кожного завдання, як правило, на його стеку, а не в статичних або глобальних змінних.
Взаємовиключення:
Доступ до спільних даних серіалізується за допомогою механізмів, які забезпечують зчитування або запис спільних даних у будь-який час лише одним потоком. Необхідна велика обережність, якщо фрагмент коду має доступ до декількох спільних даних - проблеми включають перегони, тупикові місця, живлення, голодування та інші проблеми, перелічені у багатьох підручниках з операційними системами.
Місцеве сховище потоків:
Змінні локалізовані так, що кожен потік має свою приватну копію. Ці змінні зберігають свої значення через підпрограму та інші межі коду та є безпечними для потоків, оскільки вони локальні для кожного потоку, навіть незважаючи на те, що код, який звертається до них, може бути ретранслятором.
Атомні операції:
Доступ до загальних даних здійснюється за допомогою атомних операцій, які не можуть бути перервані іншими потоками. Зазвичай для цього потрібно використовувати спеціальні інструкції з мовної машини, які можуть бути доступні в бібліотеці виконання. Оскільки операції є атомними, спільні дані завжди зберігаються у дійсному стані, незалежно від того, які інші потоки мають до нього доступ. Атомні операції складають основу багатьох механізмів фіксації ниток.
читати далі:
http://en.wikipedia.org/wiki/Thread_safety
німецькою мовою: http://de.wikipedia.org/wiki/Threadsicherheit
французькою мовою: http://fr.wikipedia.org/wiki/Threadsafe (дуже короткий)
Безпечний для потоків код - це код, який буде працювати, навіть якщо багато ниток виконують його одночасно.
Більш інформативним є питання про те, що робить код не потоковим, а відповідь - це чотири умови, які повинні бути правдивими ... Уявіть собі наступний код (і це машинний переклад мови)
totalRequests = totalRequests + 1
MOV EAX, [totalRequests] // load memory for tot Requests into register
INC EAX // update register
MOV [totalRequests], EAX // store updated value back to memory
Мені подобається визначення програми Java Concurrency Java Brac Goetz у практиці за її всебічність
"Клас безпечний для потоків, якщо він веде себе правильно при доступі з декількох потоків, незалежно від планування чи переплетення виконання цих потоків середовищем виконання та без додаткової синхронізації чи іншої координації з боку викликового коду. "
Як зазначали інші, безпека потоку означає, що фрагмент коду буде працювати без помилок, якщо його використовуватиме більше ніж один потік одночасно.
Варто усвідомлювати, що це іноді відбувається за рахунок витрат на комп’ютер, час і складніше кодування, тому це не завжди бажано. Якщо клас можна безпечно використовувати лише на одній нитці, можливо, це буде краще зробити.
Наприклад, у Java є два класи, які майже еквівалентні, StringBuffer
і StringBuilder
. Різниця полягає в тому, що StringBuffer
безпечно для потоків, тому один примірник StringBuffer
може використовуватись декількома потоками одночасно. StringBuilder
не є безпечним для потоків і розроблений як заміна більш високої продуктивності для тих випадків (переважна більшість), коли String будується лише одним потоком.
Найпростіший спосіб зрозуміти це те, що робить код не безпечним для потоків. Існує два основних питання, які змусять програму з потоковим потоком мати небажану поведінку.
Доступ до загальної змінної без блокування
Ця змінна може бути змінена іншим потоком під час виконання функції. Ви хочете запобігти це за допомогою блокувального механізму, щоб бути впевненим у поведінці вашої функції. Загальне правило - тримати замок якнайшвидше.
Тупик, спричинений взаємною залежністю від загальної змінної
Якщо у вас є дві спільні змінні A і B. В одній функції ви блокуєте A спочатку, потім пізніше ви блокуєте B. В іншій функції ви починаєте блокувати B, а через деякий час ви блокуєте A. Це є потенційним тупиком, коли перша функція буде чекати розблокування B, коли друга функція чекатиме розблокування. Це питання, ймовірно, не виникне у вашому середовищі розробки та лише час від часу. Щоб цього уникнути, всі замки завжди повинні бути в одному порядку.
Так і ні.
Безпека нитки - це трохи більше, ніж просто переконатися, що доступ до ваших спільних даних отримує лише одна нитка за один раз. Ви повинні забезпечити послідовний доступ до спільних даних, водночас уникаючи гоночних умов , тупиків , мешканців та голодування ресурсів .
Непередбачувані результати, коли працює кілька потоків, не є обов'язковою умовою безпечного потокового коду, але це часто побічний продукт. Наприклад, у вас може бути створена схема виробник-споживач із спільною чергою, одним потоком виробника та кількома потоками споживачів, і потік даних може бути цілком передбачуваним. Якщо ви почнете представляти більше споживачів, ви побачите більше випадкових результатів.
По суті, багато речей можуть піти не так у багатопотоковому середовищі (впорядковані інструкції, частково побудовані об'єкти, та сама змінна, що має різні значення в різних потоках через кешування на рівні процесора тощо).
Мені подобається визначення, яке дав Java Concurrency в практиці :
[Частина коду] є безпечною для потоків, якщо вона веде себе правильно при доступі з декількох потоків, незалежно від планування чи переплетення виконання цих потоків середовищем виконання та без додаткової синхронізації чи іншої координації з боку код виклику.
При правильно вони означають , що програма веде себе відповідно до своїх вимог.
Надуманий приклад
Уявіть, що ви реалізуєте лічильник. Можна сказати, що він поводиться правильно, якщо:
counter.next()
ніколи не повертає значення, яке вже було повернено раніше (для простоти ми вважаємо, що немає переповнення тощо)Захисний лічильник потоків поводиться за цими правилами, незалежно від того, скільки потоків одночасно отримує доступ до нього (що, як правило, не є випадком наївного впровадження).
Примітка: перехресне повідомлення програмистів
Просто - код буде нормально працювати, якщо багато потоків виконує цей код одночасно.
Не плутайте безпеку нитки з детермінізмом. Ниткозахисний код також може бути недетермінованим. Зважаючи на складність налагодження проблем з потоковим кодом, це, мабуть, нормальний випадок. :-)
Безпека нитки просто забезпечує те, що коли нитка змінює або читає спільні дані, жодна інша нитка не може отримати доступ до неї таким чином, що змінює дані. Якщо ваш код залежить від певного замовлення на виконання для коректності, тоді для забезпечення безпеки потоку вам потрібні інші механізми синхронізації, ніж ті, що необхідні для безпеки потоку.
Я хотів би додати ще трохи інформації на додаток до інших хороших відповідей.
Безпека нитки передбачає, що кілька потоків можуть записувати / читати дані в один об’єкт без помилок невідповідності пам'яті. У дуже багатопотоковій програмі безпечна програма для потоку не викликає побічних ефектів для спільних даних .
Перегляньте це питання SE для більш детальної інформації:
Програма безпечної нитки гарантує послідовність пам’яті .
З сторінки документації Oracle на розширеному сумісному API:
Властивості послідовності пам'яті:
Розділ 17 специфікації мови Java ™ визначає відношення до операцій пам'яті, таких як читання та запис спільних змінних. Результати запису одним потоком гарантовано будуть видимими для прочитаного іншим потоком, лише якщо операція запису відбудеться - перед операцією читання .
synchronized
І volatile
конструкції, а також Thread.start()
і Thread.join()
методи, форма може відбувається, перш за , ніж відносини.
Методи всіх класів у java.util.concurrent
та його підпакетів поширюють ці гарантії на синхронізацію вищого рівня. Зокрема:
Runnable
до того, як Executor
відбудеться, до початку її виконання. Аналогічно для Callables, поданий до ExecutorService
.Future
діями, що відбуваються раніше, після отримання результату через Future.get()
інший потік.Lock.unlock, Semaphore.release, and CountDownLatch.countDown
відбудуться перед успішним методом "придбання", наприклад, Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await
над тим самим об'єктом синхронізатора в іншому потоці.Exchanger
дії, що передують перед exchange()
кожним потоком, відбуваються раніше, ніж ті, що слідують за відповідним обміном () в іншому потоці.CyclicBarrier.await
та Phaser.awaitAdvance
(як і його варіанти) відбуваються перед діями, виконаними бар'єрною дією, і дії, що виконуються бар'єрною дією, відбуваються перед тим, як дії, наступні після успішного повернення з відповідних, очікують в інших потоках.Щоб заповнити інші відповіді:
Синхронізація викликає занепокоєння, коли код у вашому методі робить одну з двох речей:
Це означає, що змінні, визначені ВІД вашого методу, завжди є безпечними. Кожен виклик методу має свою версію цих змінних. Якщо метод викликається іншим потоком або тим самим потоком, або навіть якщо метод викликає себе (рекурсія), значення цих змінних не поділяються.
Планування ниток не гарантується як круглообертове . Завдання може повністю зависати ЦП за рахунок ниток того самого пріоритету. Ви можете використовувати Thread.yield (), щоб мати совість. Ви можете використовувати (в java) Thread.setPriority (Thread.NORM_PRIORITY-1), щоб знизити пріоритет потоку
Плюс остерігайтеся:
Так і так. Це означає, що дані не змінюються одночасно більш ніж одним потоком. Однак ваша програма може працювати, як очікувалося, і здаватися безпечною для потоків, навіть якщо вона принципово ні.
Зауважимо, що непередбачуваність результатів є наслідком "умов перегонів", які, ймовірно, призводять до зміни даних у порядку, відмінному від очікуваного.
Відповімо на це на прикладі:
class NonThreadSafe {
private int counter = 0;
public boolean countTo10() {
count = count + 1;
return (count == 10);
}
countTo10
Метод додає один до прилавка , а потім повертає істину , якщо число досягло 10. Він повинен повертати тільки істинний раз.
Це буде працювати до тих пір, поки лише один потік виконує код. Якщо код одночасно працює двома потоками, можуть виникнути різні проблеми.
Наприклад, якщо число починається з 9, один потік може додати 1 для підрахунку (10), але тоді другий потік може ввести метод і знову додати 1 (що робить 11), перш ніж перший потік має шанс виконати порівняння з 10 Тоді обидві нитки роблять порівняння і виявляють, що кількість дорівнює 11, і жодна з них не повертає істину.
Тож цей код не є безпечним для потоків.
По суті, всі проблеми з багатопотоковою ниткою викликані деякою різницею подібної проблеми.
Рішення полягає в тому, щоб додати та порівняння не можна розділити (наприклад, оточення двох операторів якимсь кодом синхронізації) або розробити рішення, яке не потребує двох операцій. Такий код буде безпечним для потоків.
Принаймні, на C ++, я вважаю, що безпека для потоків є дещо помилкою, оскільки вона залишає багато імені. Щоб бути безпечним для потоків, код, як правило, повинен бути проактивним щодо нього. Це взагалі не пасивна якість.
Щоб клас захищений від протектора, він повинен мати "додаткові" функції, що додають накладні витрати. Ці функції є частиною реалізації класу і взагалі кажучи, приховані від інтерфейсу. Тобто, різні потоки можуть отримувати доступ до будь-якого з членів класу, не турбуючись про конфлікт з одночасним доступом іншою ниткою І можуть робити це дуже ледачим способом, використовуючи звичайний старий звичайний стиль кодування людини, не вимагаючи цього весь той шалений синхронізаційний матеріал, який уже закочується в кишки коду, що викликається.
І саме тому деякі люди вважають за краще використовувати термін внутрішньо синхронізований .
Є три основні набори термінології для цих ідей, з якими я стикався. Перший та історично популярніший (але гірший):
Другий (і кращий):
Третя частина:
потоковий безпечний ~ потоковий захист ~ внутрішньо синхронізований
Прикладом внутрішньо синхронізованої (а.к. безпечної для потоків чи потоку протоколу ) є ресторану, де господар зустрічає вас у дверях і забороняє вас у черзі. Господар є частиною механізму ресторану для роботи з кількома клієнтами і може використовувати деякі досить хитрі прийоми для оптимізації розміщення клієнтів, які чекають, наприклад, врахування розміру їхньої партії або скільки часу вони виглядають, як у них є або навіть бронювати по телефону. Ресторан внутрішньо синхронізований, оскільки все це є частиною інтерфейсу для взаємодії з ним.
не є безпечним для потоків (але приємно) ~ сумісний з потоком ~ зовнішньо синхронізований ~ вільно-потоковий
Припустимо, ви йдете в банк. Існує лінія, тобто суперечка для банківських касирів. Оскільки ти не дикун, ти визнаєш, що найкраще робити в розпал суперечок за ресурс - це чергуватись як цивілізована істота. Ніхто технічно вас не змушує цього робити. Ми сподіваємось, що у вас є необхідне соціальне програмування, щоб зробити це самостійно. У цьому сенсі лобі банку зовні синхронізоване. Чи варто говорити, що це небезпечно для ниток? це те, що мається на увазі, якщо ви йдете з потокобезпечна , поточно-небезпечних біполярним набором термінології. Це не дуже вдалий набір термінів. Краща термінологія зовнішньо синхронізована,Лобі банку не є ворожим до доступу до нього декількох клієнтів, але це також не виконує роботу з їх синхронізацією. Замовники роблять це самі.
Це також називається "вільна різьба", де "вільна" - це як "вільна від вошей" - або в цьому випадку замки. Ну, точніше, примітиви синхронізації. Це не означає, що код може працювати на декількох потоках без цих примітивів. Це просто означає, що він не постачається з уже встановленими, і ви, користувач коду, вирішувати встановити їх самостійно, як ви вважаєте за потрібне. Установка власних примітивів для синхронізації може бути складною і вимагає серйозного роздуму над кодом, але також може призвести до найшвидшої програми, дозволяючи налаштувати виконання програми на сьогоднішніх гіперточених процесорах.
не безпечно для потоків (і погано) ~ нитка ворожа ~ несинхронізація
Прикладом щоденної аналогії системи, що ворогує з нитками, є деякий ривок зі спортивним автомобілем, який відмовляється використовувати свої блималки та міняє смуги волі-неволі. Їх стиль водіння є ворожим або несихронізованим, оскільки у вас немає способу узгодити їх, і це може призвести до суперечки на одній смузі, без вирішення, і, таким чином, до ДТП, коли дві машини намагаються зайняти той самий простір, без жодного протоколу до запобігти цьому. Цю закономірність можна також розглядати як антисоціальну, що я вважаю за краще, оскільки вона менш специфічна для потоків і настільки загалом застосовна для багатьох областей програмування.
Перший і найдавніший термінологічний набір не дозволяє чіткіше розрізнити між ворожістю потоку і сумісністю потоку . Сумісність нитки є більш пасивною, ніж так звана безпека потоку, але це не означає, що названий код небезпечний для одночасного використання потоку. Це просто означає, що це пасивно щодо синхронізації, яка б це дозволила, відклавши його до виклику коду, а не надавати його як частину його внутрішньої реалізації. Сумісність з темою - це те, як, мабуть, у більшості випадків пишеться код за замовчуванням, але це, на жаль, часто помилково вважається ниткою небезпечною, як ніби це по суті антибезпека, що є головним моментом плутанини для програмістів.
ПРИМІТКА: Багато посібників із програмного забезпечення насправді використовують термін "безпечний для потоків" для позначення "сумісний з потоками", додаючи ще більше плутанини тому, що вже було безладно! Я уникаю термінів "безпечно для потоків" та "безпечно для потоків" за будь-яку ціну саме з цієї причини, оскільки одні джерела називатимуть щось "безпечним для потоків", а інші називають "безпечним для потоку", оскільки вони не можуть погодитися щодо того, чи потрібно вам відповідати деяким додатковим стандартам безпеки (примітиви синхронізації), або просто НЕ вороже вважатись "безпечним". Тому уникайте цих термінів і використовуйте натомість розумніші терміни, щоб уникнути небезпечних помилок з іншими інженерами.
По суті, наша мета - підірвати хаос.
Ми робимо це, створюючи детерміновані системи, на які можна покластися. Детермінізм дорогий, головним чином через можливі витрати втрати паралелізму, конвеєра та переупорядкування. Ми намагаємось мінімізувати кількість детермінізму, який нам потрібен, щоб утримати наші витрати низькими, а також уникаючи прийняття рішень, які ще більше розмиють те, що мало детермінізму ми можемо собі дозволити.
Синхронізація потоків - це збільшення порядку та зменшення хаосу. Рівні, на яких ви це робите, відповідають умовам, згаданим вище. Найвищий рівень означає, що система щоразу поводиться абсолютно передбачувано. Другий рівень означає, що система поводиться досить добре, що код виклику може надійно виявити непередбачуваність. Наприклад, помилкове пробудження в змінній стану або неможливість блокування мютексу, оскільки воно не готове. Третій рівень означає, що система не веде себе досить добре, щоб грати з ким-небудь іншим, і її можна буде ВСІХТЕ запускати однопотоковою без нанесення хаосу.
Замість того, щоб вважати код або класи безпечними для потоків чи ні, я вважаю, що корисніше думати про дії як про безпеку потоку. Дві дії є безпечними для потоків, якщо вони будуть вестись так, як зазначено під час запуску з довільних контекстів нарізки. У багатьох випадках заняття підтримуватимуть деякі комбінації дій у безпечній формі, а інші - ні.
Наприклад, багато колекцій, таких як списки масивів і хеш-набори, гарантують, що якщо вони спочатку отримують доступ виключно одним потоком, і вони ніколи не змінюються після того, як посилання стане видимою для будь-яких інших потоків, вони можуть бути прочитані в довільному порядку будь-якою комбінацією ниток без втручання.
Що ще цікавіше, деякі колекції хеш-наборів, такі як оригінальна не-загальна в .NET, можуть гарантувати, що доки жоден елемент не буде видалений, і за умови, що до них пишеться лише один потік, будь-який потік, який намагається читання колекції буде вести себе як би доступ до колекції, де оновлення можуть затримуватися і відбуватись у довільному порядку, але в іншому випадку вони будуть вести себе нормально. Якщо потік №1 додає X, а потім Y, а нитка №2 шукає і бачить Y, а потім X, для потоку №2 можна було б побачити, що Y існує, але X немає; чи така поведінка є "безпечною для потоків" чи ні, залежатиме від того, чи готовий нитка №2 боротися з цією можливістю.
На завершення деякі класи, особливо блокування бібліотек зв’язку, можуть мати метод "закрити" або "розпорядитись", який є безпечним для потоків стосовно всіх інших методів, але немає інших методів, безпечних для потоків стосовно один одного. Якщо потік виконує блокування запиту читання, а користувач програми натискає "скасувати", то потік, який намагається виконати зчитування, не може видавати закритий запит. Запит на закриття / розпорядження, однак, може асинхронно встановити прапор, що призведе до скасування запиту читання якнайшвидше. Щойно закриття виконується на будь-якій нитці, об'єкт стає марним, і всі спроби майбутніх дій негайно зазнають краху,
Найпростішими словами: P Якщо безпечно виконувати кілька потоків на блоці коду, він є безпечним для потоків *
* діють умови
Умови згадуються іншими відповідями, такими як 1. Результат повинен бути однаковим, якщо ви виконаєте один потік або кілька потоків над ним тощо.