Які уроки ви засвоїли з проекту, який майже / насправді провалився через погану багатопотоковість? [зачинено]


11

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

Іноді, рамка накладає певну модель різьблення, яка ускладнює порядок.

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

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

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

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

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

Додано

Найкраща аналогія, яку я отримав від іншої команди (не пов'язаної з моїми минулими чи теперішніми проектами), - це "занести дані в базу даних". ("База даних" з посиланням на централізацію та атомні оновлення.) У графічному інтерфейсі, який фрагментований на кілька представлень, які працюють на одній "головній нитці", а всі важкі підйоми, що не є GUI, виконуються в окремих робочих потоках, дані програми повинні мати зберігатись в єдиній площині, яка діє як База даних, і нехай "База даних" обробляє всі "атомні оновлення", що включають нетривіальні залежності даних. Всі інші частини графічного інтерфейсу просто обробляють малюнок екрану і більше нічого. Частини користувальницького інтерфейсу можуть кешувати матеріали, і користувач не помітить, якщо він затягся на частку секунди, якщо він розроблений належним чином. Ця "база даних" також відома як "документ" в архітектурі перегляду документів. На жаль - ні, мій додаток насправді зберігає всі дані у Переглядах. Я не знаю, чому так було.

Співробітники:

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



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

Відповіді:


13

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

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


+1 для "не ділити пам'ять (або інші ресурси) між потоками, крім повідомлень, які повинні бути асинхронними;"
Неманья Трифунович

1
Тільки шлях? А як щодо непорушних типів даних?
Aaronaught

is that in a multithreaded program the scheduler is a sneaky swine that hates you.- ні, це не так, він робить саме те, що ви йому сказали робити :)
mattnz

@Aaronaught: Глобальні значення, передані за посиланням, навіть якщо вони незмінні, все ще потребують глобальної GC, і це знову представляє цілу купу глобальних ресурсів. Можливість використовувати управління пам’яттю за нитками - це приємно, оскільки дозволяє позбутися цілої кількості глобальних блокувань.
Стипендіати доналу

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

6

Ось декілька основних уроків, про які я зараз можу подумати (не з провальних проектів, а з реальних питань, що спостерігаються на реальних проектах):

  • Постарайтеся уникати блокування дзвінків, тримаючи спільний ресурс. Поширений шаблон тупикового зв'язку - це те, що схоплює файли mutex, здійснює зворотний дзвінок, блокує зворотний виклик на цьому ж файлі.
  • Захистіть доступ до будь-яких спільних структур даних за допомогою мутексу / критичного розділу (або використовуйте блоки, не заблоковані, але не вигадуйте свої власні!)
  • Не допускайте атомності - використовуйте атомні API (наприклад, InterlockedIncrement).
  • RTFM щодо безпеки потоку бібліотек, об'єктів або API, які ви використовуєте.
  • Скористайтеся наявними примітивами синхронізації, наприклад, подіями, семафорами. (Але звертайте пильну увагу, використовуючи їх, якщо ви знаєте, що ви перебуваєте в хорошому стані. Я бачив багато прикладів подій, що сигналізують про неправильний стан, так що події чи дані можуть загубитися)
  • Припустимо, потоки можуть виконуватися одночасно та / або в будь-якому порядку, і цей контекст може перемикатися між потоками в будь-який час (якщо тільки в ОС, яка дає інші гарантії).

6
  • Весь ваш GUI- проект слід викликати лише з основного потоку . В основному, ви не повинні ставити жодного (.net) "виклику" у вашому графічному інтерфейсі. Багатопотоковість повинна застрявати в окремих проектах, які працюють з більш повільним доступом до даних.

Ми успадкували частину, де проект GUI використовує десяток ниток. Це не дає нічого, крім проблем. Тупики, гоночні проблеми, перехресні дзвінки GUI ...


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

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

Я не думаю, що це правило є загальноприйнятим. Так, ви ніколи не повинні викликати GUI-код з іншого потоку. Але як ви розподіляєте класи в папках / проектах / складаннях - це самостійне рішення.
nikie

1

У Java 5 і пізніших версіях є виконавці, які покликані полегшити життя при роботі з програмами стилю для багатопотокового з'єднання.

Використовуйте ті, це зніме сильний біль.

(і так, це я дізнався з проекту :))


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

1

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

  • Правило №1: KISS - Якщо вам не потрібна нитка, не обертайте її. Максимально серіалізуйте.
  • Правило №2: Не порушуйте №1.
  • # 3 Якщо ви не можете довести через огляд, що це правильно, це не так.

+1 для правила 1. Я працював над проектом, який спочатку збирався заблокувати, поки не завершився інший потік - по суті, виклик методу! На щастя, ми вирішили проти такого підходу.
Майкл К

№3 FTW. Краще витратити години на боротьбу з діаграмами блокування часу або тим, що ви використовуєте, щоб довести, що це добре, ніж місяці, цікаво, чому він іноді розпадається.

1

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

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

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


0

Спробуйте зробити це ще раз.

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

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

Ще одна річ, про яку ви дізнаєтесь більше - це заблокувати вільні структури даних.

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


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

1
налагоджувачі в основному марні в багатопотоковому середовищі.
Pemdas

У мене вже є багатопотокові трекери виконання, які підказують мені, в чому проблема, але не допоможуть мені її вирішити. Суть моєї проблеми полягає в тому, що "згідно з сучасним дизайном, я не можу передавати повідомлення X в об'єкт Y таким чином (послідовність); його потрібно додати до гігантської черги, і воно врешті-решт буде оброблене; але через це , немає жодного способу, щоб повідомлення з’являлися користувачеві в потрібний час - це завжди буде анахронічно і зробить користувача дуже, дуже заплутаним. Можливо, вам навіть потрібно буде додати панелі прогресу, скасувати кнопки або повідомлення про помилки в місця, які не повинні ' не маю таких ".
rwong

0

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


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

Розв’язання задачі оптимізації (як мінімізація f (x)) часто реалізується шляхом надання покажчика на функцію f (x) до процедури оптимізації, яка "передзвонює", шукаючи мінімум. Як би ви це зробили без зворотного дзвінка?
Quant_dev

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

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

@Donal Fellows: Якщо під час зворотного дзвінка потрібно заблокувати замовлення, я б сказав, що у вас є вада дизайну. Якщо насправді немає іншого способу, то так, це обов'язково документуйте! Так само, як ви б документували, якщо зворотний виклик буде викликаний у фоновому потоці. Це частина інтерфейсу.
nikie
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.