Чи є застарілі практики для багатопотокового та багатопроцесорного програмування, які я більше не повинен використовувати?


36

На початку FORTRAN та BASIC фактично всі програми були написані з заявами GOTO. Результатом став код спагетті, а рішення - структуроване програмування.

Аналогічно, покажчики можуть мати труднощі з управління характеристиками в наших програмах. C ++ розпочався з великою кількістю покажчиків, але рекомендується використовувати посилання. Бібліотеки, такі як STL, можуть зменшити частину нашої залежності. Існують також ідіоми для створення розумних покажчиків, які мають кращі характеристики, а деякі версії C ++ дозволяють посилатися та керувати кодом.

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

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

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

th_id = omp_get_thread_num();
#pragma omp critical
{
  cout << "Hello World from thread " << th_id << '\n';
}

Цей приклад - уривок з: http://en.wikipedia.org/wiki/OpenMP

Крім того, подібний захист потоків один від одного за допомогою семафорів з функціями wait () і signal () може виглядати так:

wait(sem);
th_id = get_thread_num();
cout << "Hello World from thread " << th_id << '\n';
signal(sem);

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

Чи усувають такі системи, як OpenMP, проблеми з семафорами?
Вони переносять проблему кудись інше?
Як перетворити свій улюблений семафор за допомогою алгоритму, щоб більше не використовувати семафори?


Про що саме ви говорите? Що ви бачили?
svick

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

1
Ого, велика редакція. Я прийняв удар у відповідь. Питання все ще блукає через GOTO, покажчики, успадкування та поліморфізм, але у своїй відповіді я відклав ці питання в сторону і зосередився на питанні "застарілої практики".
Стюарт Маркс

Відповіді:


15

Чи існують паралельні методи і практики програмування, які більше не слід використовувати? Я б сказав так .

Однією з ранніх методів одночасного програмування, яка здається рідкісною в наші дні, є програмоване керування перервами . Так працював UNIX у 1970-х. Дивіться коментар Левів щодо UNIX або Бахового проектування Операційної системи UNIX . Якщо коротко, метод полягає в тимчасовому припиненні переривань під час маніпулювання структурою даних, а потім відновлення переривань після цього. Сторінка людини BSD spl (9)є приклад цього стилю кодування. Зауважте, що переривання орієнтовані на апаратне забезпечення, і код втілює неявну залежність між видом апаратного переривання та структурами даних, пов'язаними з цим обладнанням. Наприклад, код, який маніпулює буферами дискових вводу / виводу, повинен призупиняти переривання з апаратних засобів дискового контролера під час роботи з цими буферами.

Цей стиль програмування використовувався операційними системами на універсальному програмному забезпеченні. Набагато рідше застосунки мали справу з перебоями. Деякі ОС мали переривання програмного забезпечення, і я думаю, що люди намагалися будувати вершини для різьблення чи корегування, але це було не дуже широко. (Звичайно, це не в світі UNIX.) Я підозрюю, що програмування в стилі переривання сьогодні обмежується невеликими вбудованими системами або системами реального часу.

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

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

Існують додаткові конструкції бібліотек, такі, як у java.util.concurrentпакеті Java , який включає в себе різноманітні структури, що є дуже одночасно, і структури об'єднання потоків. Вони можуть поєднуватися з додатковими методами, такими як обмеження ниток та ефективна незмінність. Див. Розділ Конкурс Java на практиці від Goetz et. ін. для подальшого обговорення. На жаль, багато програмістів досі перекочують власні структури даних із блокуваннями та умовами, коли їм справді слід просто використовувати щось на кшталт ConcurrentHashMap, де автори бібліотеки вже здійснили важкий підйом.

Все, що вище, має деякі суттєві характеристики: вони мають декілька потоків управління, які взаємодіють над загальносвітовим, змінним станом . Проблема полягає в тому, що програмування в цьому стилі все ще дуже схильне до помилок. Невелика помилка досить легко залишиться непоміченою, що призводить до неправильної поведінки, яку важко відтворити та діагностувати. Можливо, жоден програміст не «достатньо ретельний і старанний», щоб розвивати великі системи таким чином. Принаймні, дуже мало. Отже, я б сказав, що слід уникати багатопотокового програмування зі спільним, змінним станом, якщо це можливо.

На жаль, не зовсім зрозуміло, чи можна цього уникнути у всіх випадках. Багато програмування все ще робиться таким чином. Було б непогано побачити це, витіснене чимось іншим. Відповіді Jarrod Roberson та davidk01 вказують на такі методи, як незмінні дані, функціональне програмування, STM та передача повідомлень. Їх можна багато рекомендувати, і всі вони активно розвиваються. Але я не думаю, що вони поки що повністю замінили добрий старомодний загальнозмінений стан.

EDIT: ось моя відповідь на конкретні запитання наприкінці.

Я мало знаю про OpenMP. Моє враження, що це може бути дуже ефективним для дуже паралельних проблем, таких як чисельне моделювання. Але це не здається загальним призначенням. Конструкції семафору здаються досить низькими і вимагають від програміста підтримувати зв’язок між семафорами та спільними структурами даних з усіма описаними вище проблемами.

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


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

+1 для java.util.concurrent і погодився на коментар - він знаходиться в JDK з 1,5 року, і я рідко, якщо коли-небудь бачу, що він використовується.
MebAlone

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

Я не думаю, що це точно сказати: "Семафори - це випередження перерв, оскільки вони є програмними конструкціями (не пов'язаними з обладнанням) ". Семафори залежать від процесора, щоб реалізувати інструкцію Порівняти і замінити , або це багатоядерні варіанти .
Джош Пірс

@JoshPearce Звичайно, семафори реалізовані за допомогою апаратних конструкцій, але вони є абстракцією , незалежною від будь-якої конкретної апаратної конструкції, наприклад, CAS, тестування та встановлення, cmpxchng тощо.
Stuart Marks

28

Відповідь на запитання

Загальний консенсус для загального стану, що змінюється, є Bad ™, а незмінний стан - Good ™, який знову і знову підтверджується точним і правдивим функціональними мовами та імперативними мовами.

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

Порушена приміщення

Це питання ґрунтується на порівнянні з хибною передумовою; це GOTOбуло актуальною проблемою, і це було загалом недооціненим, як Intergalatic Universal Board of Language Designers and Software Engineering Union ©! Без GOTOмеханізму ASM не працював би взагалі. Так само і з припущенням, що сировинні покажчики є проблемою C або C ++, а також те, наскільки розумні покажчики є панацеєю, вони не є.

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

Освіта - це панацея

Програмісти ідіотів - це те, що було deprecated, кожна популярна мова все ще має GOTOконструкцію прямо чи опосередковано, і це best practiceпри правильному використанні в кожній мові, що має такий тип конструкцій.

ПРИКЛАД: Java має мітки, і try/catch/finallyобидва вони безпосередньо працюють як GOTOзаяви.

Більшість Java-програмістів, з якими я розмовляю, навіть не знаю, що immutableнасправді означає, що вони повторюються the String class is immutableіз зомбі, як виглядають у їхніх очах. Вони точно не знають, як правильно використовувати finalключове слово для створення immutableкласу. Тож я впевнений, що вони не мають поняття, чому передача повідомлень за допомогою незмінних повідомлень настільки велика і чому стан спільного змінення настільки не великий.


3
+1 Відмінна відповідь, чітко написана та чітка вказівка ​​основної моделі мутаційного стану. IUBLDSEU має стати мемом :)
Діббеке

2
GOTO - це кодове слово для "будь-ласка, насправді, будь ласка, розпочніть тут полум’яну війну, я подвійний собака наважуюся на вас". Це питання розкриває полум’я, але насправді не дає гарної відповіді. Почесні згадки про функціональне програмування та незмінність чудові, але в цих твердженнях немає жодної мети.
Еван Плейс

1
Це здається суперечливою відповіддю. Спочатку ви говорите "А погано, Б - добре", потім ви говорите "Ідіоти були застарілими". Чи не те саме стосується першого абзацу? Чи не можу я просто взяти цю останню частину вашої відповіді і сказати: "Спільний стан, що змінюється, є найкращою практикою при правильному використанні кожною мовою". Також "доказ" - дуже сильне слово. Не слід використовувати його, якщо у вас немає справжніх доказів.
luiscubal

2
Мені не було наміру розпочати полум’яну війну. Поки Джаррод не відреагував на мій коментар, подумав, що GOTO не є суперечливим і буде добре працювати за аналогією. Коли я писав питання, мені це не прийшло в голову, але Dijkstra опинився на нульовому рівні як на GOTO, так і на семафорах. Едсгер Дайкстра здається мені гігантом, і йому приписують винахід семафорів (1965) та початку (1968) наукових робіт про GOTO. Метод адвокатури Дейкстри часто був жорстоким і конфронтаційним. Суперечка / конфронтація працювала на нього, але я просто хочу уявити про можливі альтернативи семафору.
DeveloperDon

1
У багатьох програмах передбачається моделювати речі, які в реальному світі є незмінними. Якщо о 5:37 ранку об’єкт № 451 в цей момент утримує стан чогось у реальному світі (5:37 ранку), а стан реального речей згодом змінюється, то можлива або особистість об'єкта, що представляє стан реального речей, що має бути незмінним (тобто річ завжди буде представлена ​​об'єктом № 451) або для об'єкта № 451 незмінним, але не тим і іншим. У багатьох випадках мати особистість непорушною буде корисніше, ніж те, щоб об'єкт № 451 був незмінним.
supercat

27

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

Erlang використовує передачу повідомлень та агентів для одночасності, і це простіша модель роботи, ніж STM. Під час передачі повідомлень у вас немає абсолютно ніяких замків і семафорів, про які слід турбуватися, оскільки кожен агент працює у власному міні-Всесвіті, тому немає перегонів, пов’язаних з даними. У вас все ще є якісь дивні кейси, але вони ніде не такі складні, як живіт і тупики. Мови JVM можуть скористатися Akka та отримати всі переваги передачі повідомлень та акторів, але на відміну від Erlang, JVM не має вбудованої підтримки для акторів, тому наприкінці дня Akka все ще використовує нитки та блокування, але ви як програмісту не потрібно про це турбуватися.

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

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


+1 за новим терміном "волохаті деталі". LOL людина. Я просто не можу перестати сміятися над цим новим терміном. Я думаю, я зараз буду використовувати "волосатий код".
Саїд Неаматі

1
@Saeed: Я чув цей вираз і раніше, це не так вже й рідко. Я згоден, хоча це смішно :-)
Камерон

1
Гарна відповідь. .NET CLI нібито також підтримує сигналізацію (на відміну від блокування), але я ще не натрапив на приклад, коли це повністю замінило блокування. Я не впевнений, чи має значення асинхроніка. Якщо ви говорите про таких платформах, як Javascript / NodeJs, вони насправді однопоточні і краще лише при великих навантаженнях вводу-виводу, оскільки вони набагато менш сприйнятливі до максимальних обмежень ресурсів (тобто для тонни викидних контекстів). У процесах, що інтенсивно завантажуються процесором, використання програм програмування async мало / ніякої користі.
Еван Плейс

1
Цікава відповідь, я раніше не стикався з ф'ючерсами . Також зауважте, що у таких системах передачі повідомлень, як Erlang , у вас все ще можуть бути тупикові місця та неполадки . CSP дозволяє вам офіційно міркувати про тупик та вихід з життя, але це не заважає самому.
Марк Бут

1
Я б додав Lock free і зачекав вільні структури даних до цього списку.
каменний метал

3

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

Це стосується керуючих структур: ifs, fors і even try- catchблоки - це лише абстракції над gotos. Ці абстракції майже завжди корисні, оскільки роблять ваш код більш читабельним. Але є випадки, коли вам все одно доведеться користуватися goto(наприклад, якщо ви пишете збірку вручну).

Це стосується також управління пам’яттю: розумні покажчики C ++ та GC - це абстракції над необробленими покажчиками та ручне де-розподіл пам'яті. І іноді такі абстракції не підходять, наприклад, коли вам справді потрібні максимальні показники.

І те ж саме стосується багатопотокової передачі: такі речі, як ф'ючерси та актори, є лише абстракціями над нитками, семафорами, мютексами та інструкціями CAS. Такі абстракції можуть допомогти вам зробити ваш код набагато більш читабельним, а також допоможуть уникнути помилок. Але іноді вони просто не підходять.

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


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

2

Так, але ви, швидше за все, не натрапите на деякі з них.

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

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

Тож, можливо, застаріла практика - писати код одночасності самостійно, а не використовувати сучасні, добре перевірені бібліотеки.


Спасибі. Схоже, існує великий потенціал для використання одночасного програмування, але це може бути поле Пандори, якщо його не використовувати дисципліновано.
DeveloperDon

2

Apple Grand Central Dispatch - це елегантна абстракція, яка змінила моє мислення про одночасність. Моє зосередження уваги на чергах робить впровадження асинхронної логіки на порядок простішим, на мій скромний досвід.

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


1

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

Це одна з причин, чому асинхронні або на основі завдань (наприклад, Grand Central Dispatch або Intel TBB) є більш популярними, вони запускають завдання код 1 за один раз, отримуючи його до кінця, перш ніж перейти до наступного - однак, ви повинні кодувати кожне кожне завдання займає мало часу, якщо ви не хочете вкрутити конструкцію (тобто паралельні завдання справді стоять у черзі). Завдання, що вимагають процесора, передаються до альтернативного ядра процесора, а не обробляються на одному потоці, обробляючи всі завдання. Це також простіше в управлінні, якщо теж не відбувається справді багатопотокова обробка.


Класно, дякую за посилання на технології Apple та Intel. Ваша відповідь вказує на проблеми управління спорідненістю "до потоку"? Деякі проблеми з кеш-пам’яттю полегшуються, оскільки багатоядерні процесори можуть повторювати кеші L1 на ядро? Наприклад: software.intel.com/en-us/articles/… Високошвидкісний кеш для чотирьох ядер з більшою кількістю показів кешу може бути більш ніж у 4 рази швидшим, ніж одне ядро ​​з більшою кількістю пропущених кешів за тими ж даними. Матричне множення може. Випадкове планування 32 ниток на 4 ядрах не може. Давайте використаємо спорідненість і отримаємо 32 ядра.
DeveloperDon

насправді, хоча це одна і та ж проблема - спорідненість до основного просто посилається на проблему, коли завдання перескакує від ядра до ядра. Це ж питання, якщо завдання переривається, замінюється новим, тоді початкове завдання продовжується на тому ж ядрі. Приказка Intel там: кеш-хіти = швидкий, кеш пропускає = повільно, незалежно від кількості ядер. Я думаю, вони намагаються переконати вас купувати їхні фішки, а не AMD :)
gbjbaanb
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.