На що ви звертаєте увагу при налагодженні тупиків?


25

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

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

На що ви звертаєте увагу при налагодженні тупиків?


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

Стратегії, які я можу придумати, - це ведення журналу (як уже вказали кілька інших), насправді вивчаючи тупиковий графік того, хто кого чекає, що заблокує замок ( для деяких див. Stackoverflow.com/questions/3483094/… покажчики) та блокувати анотації (див. clang.llvm.org/docs/ThreadSafetyAnalysis.html ). Навіть якщо це не ваш код, ви можете спробувати переконати автора додати примітки - вони, ймовірно, знайдуть помилки та виправлять їх (можливо, включаючи ваш) у процесі.
Дон Хетч

Відповіді:


23

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

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

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

Звичайно, можливо, ви не знайдете проблеми, перш ніж у вас не вистачить часу або грошей.


4
+1 Вау, це песимістично ... хіба це не правда? Це дано, що ви не можете знайти всіх помилок. Дякую за пропозиції!
Майкл К

Брюсе, твоя характеристика "справжнього тупика" мене дивує. Я думав, що тупик між двома потоками - це коли кожна чекає на замок, який тримає інший. Здається, ваше визначення включає також випадок, коли нитка, утримуючи один замок, очікує придбання другого блокування, який зараз утримується іншим потоком. Це для мене не схоже на глухий кут; є це??
Дон Хетч

@DonHatch - я погано сформулював це. Ситуація, яку ви описуєте, не є тупиковою. Я сподівався передати безладність налагодження ситуації, яка включає в себе замок A, що тримає нитку, потім намагається придбати замок B, тоді як нитка, яка тримає замок B, намагається придбати замок A. Можливо. А може ситуація набагато складніша. Вам просто потрібно мати дуже відкриту думку про порядок придбання блокування. Вивчіть усі припущення. Нічого не довіряйте.
Брюс Едігер

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

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

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

  3. Ще одна річ, яку ви можете спробувати, - це створення бібліотеки для обгортки навколо ваших служб mutex / semaphore. Відслідковуйте, які потоки мають кожен мутекс та які нитки очікують на мютекс. Створіть моніторну нитку, яка перевіряє, як довго блокуються потоки. Запустити деяку розумну тривалість та скинути інформацію про стан, яку ви відстежуєте.

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


6

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

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

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


"Винятки можуть запобігти випуску" -> Мені шкода мов, які не мають масштабування змінних: /
Matthieu M.

1
@Matthieu: Отримати розмір змінних і фактично правильно використовувати їх може бути дві різні речі. І він поцікавився можливими проблемами загалом, не згадуючи конкретної мови. Отже, це одне, що може впливати на потік контролю.
thorsten müller

3

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

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


1
Я налагодив пару мертвих замків, як це сьогодні. Трюк полягав у тому, щоб обернути pthread_mutex_lock () макросом, який друкує функцію, номер рядка, ім'я файлу та ім'я змінної mutex (шляхом токенізації) до та після придбання блокування. Зробіть те ж саме і для pthread_mutex_unlock (). Коли я побачив, що моя нитка застигла, мені просто довелося переглянути останні два повідомлення, було дві нитки, які намагаються заблокувати, але ніколи не закінчуючи її! Тепер все, що залишилося, - це додати механізм, щоб перемикати це під час виконання. :-)
Плюменатор

3

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

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


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

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

0

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


0

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

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

І тоді є стара, але непогрішна стратегія: призначте "рівень" кожному блокуванню, починаючи з рівня 0. Якщо ви берете замок рівня 0, вам не дозволено жодних інших блокувань. Після зняття блокування рівня 1 ви можете зробити блокування рівня 0. Після вимкнення блокування 10 рівня ви можете взяти замки на рівні 9 або нижче тощо.

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

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