Насаджений багатопоточними помилками


26

У моїй новій команді, якою я керую, більшість нашого коду - це платформа, TCP-сокет і http-мережний код. Усі C ++. Більшість його походить від інших розробників, які покинули команду. Нинішні розробники в команді дуже розумні, але в основному молодші за рівнем досвіду.

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

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

Поширена помилка №1 - Виправлення проблеми одночасності, просто поставивши блокування навколо спільних даних, але забувши про те, що відбувається, коли методи не викликаються в очікуваному порядку. Ось дуже простий приклад:

void Foo::OnHttpRequestComplete(statuscode status)
{
    m_pBar->DoSomethingImportant(status);
}

void Foo::Shutdown()
{
    m_pBar->Cleanup();
    delete m_pBar;
    m_pBar=nullptr;
}

Отже, тепер у нас є помилка, в яку може зателефонувати Shutdown, поки відбувається OnHttpNetworkRequestComplete. Тестер знаходить помилку, фіксує дамп збою та призначає помилку розробнику. Він у свою чергу виправляє помилку так.

void Foo::OnHttpRequestComplete(statuscode status)
{
    AutoLock lock(m_cs);
    m_pBar->DoSomethingImportant(status);
}

void Foo::Shutdown()
{
    AutoLock lock(m_cs);
    m_pBar->Cleanup();
    delete m_pBar;
    m_pBar=nullptr;
}

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

Загальна помилка №2 - виправлення проблем із тупиком шляхом сліпого виходу з блокування, зачекайте, коли закінчиться інший потік, а потім знову введіть замок - але не обробляючи випадки, коли об’єкт щойно оновився іншим потоком!

Поширена помилка №3 - Навіть незважаючи на те, що об’єкти посилаються, послідовність відключення "звільняє" його покажчик. Але забуває дочекатися потоку, який все ще працює, щоб випустити його екземпляр. Таким чином, компоненти відключаються чисто, після чого на об'єкт викликаються помилкові або пізні зворотні дзвінки в стані, не очікуючи більше дзвінків.

Є й інші крайові випадки, але суть наступного:

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

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

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

Я розглядаю один із підходів. Для кожної важливої ​​функції платформи слід виділити єдиний потік, де всі події та зворотні зворотні дзвінки можуть бути змінені. Подібно до потоку в квартирі COM в Windows з використанням циклу повідомлень. Операції тривалого блокування все ще можуть надсилатися до потоку робочого пулу, але зворотний виклик завершення викликається в потоці компонента. Компоненти, можливо, можуть навіть мати однакову нитку. Тоді всі бібліотеки класів, що працюють всередині потоку, можна записати під припущенням про єдиний потоковий світ.

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

Мене також цікавлять будь-які інші підходи до процесу рефакторингу. У тому числі:

  1. Література чи статті про дизайнерські візерунки навколо ниток. Щось поза вступом у мютекси та семафори. Нам не потрібен масивний паралелізм або, тільки шляхи для розробки об'єктної моделі таким чином , щоб обробляти асинхронні події з інших потоків правильно .

  2. Способи побудувати схему різьблення різних компонентів, щоб було легко вивчати та розробляти рішення для. (Тобто еквівалент UML для обговорення потоків між об'єктами та класами)

  3. Навчання своєї команди розробників з питань багатопотокового коду.

  4. Що б ти зробив?


23
Деякі люди, стикаючись з проблемою, думають, що я буду використовувати багатопоточну нитку. Зараз у них є зонди для twolm
Tom Squires

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

3
Ласкаво просимо в пекло з кількома нитками. Я пишу програми для багатопотокових / паралельних програм протягом 20 років в Ada, Occam, C ++. Це ніколи нелегко, все вимагає дуже ретельної думки, і кожен, хто каже "його легко просто зроби X" - дурень, який не дуже розуміє, що відбувається. Удачі.
швидко_вінь

2
Якщо ви хочете, щоб сумісність була зроблена добре, використовуйте Erlang! По правді кажучи, те, що ви хочете, - це якась модель спільної моделі актора, де дивні кутові випадки не будуть усунені.
Захарій К

3
@DeadMG Я б заперечував, що паралельний стан спільної власності схильний до дивних кутових випадків і цього слід уникати. Але Ей, я написав книгу про Erlang
Zachary K

Відповіді:


27

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

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

Поширена помилка №3 - Навіть якщо обчислюються посилання на об'єкти, послідовність відключення "звільняє" його покажчик. Але забуває дочекатися потоку, який все ще працює, щоб випустити його екземпляр. Таким чином, компоненти відключаються чисто, після чого на об'єкт викликаються хибні або несвоєчасні зворотні виклики в стані, не очікуючи більше дзвінків.

Вся суть підрахунку посилань полягає в тому, що нитка вже випустила його екземпляр . Тому що якщо ні, то його не можна знищити, оскільки нитка все ще має посилання.

Використовуйте std::shared_ptr. Коли всі потоки випущені (і ніхто , отже, не може викликати функцію, оскільки вони не мають на неї вказівника), тоді викликається деструктор. Це гарантовано безпечно.

По-друге, використовуйте справжню бібліотеку різьблення, як, наприклад, блоки будівельних ниток Intel або бібліотеку паралельних шаблонів Microsoft. Написання власного вимагає трудомістких та ненадійних дій, і ваш код сповнений нитками деталей, які йому не потрібні. Робити власні блокування так само погано, як і власне управління пам’яттю. Вони вже втілили багато загальноприйнятих дуже корисних ідіом для нарізки, які працюють правильно для вашого використання.


Це нормальна відповідь, але не напрямок, який я шукав, оскільки він витрачає занадто багато часу на оцінку фрагмента зразкового коду, який був написаний просто для простоти (а не відображав наш реальний код у нашому продукті). Але мені цікаво один коментар, який ви зробили - "ненадійні інструменти". Що таке ненадійний інструмент? Які інструменти ви рекомендуєте?
concurrency

5
@koncurrency: Надійний інструмент - це річ, як ручне управління пам’яттю або написання власної синхронізації, де теоретично це вирішує проблему X, але насправді це так погано, що ви можете гарантувати великі помилки, і єдиний спосіб, як це можливо, вирішить проблему На розумному рівні - це велика і непропорційна вкладення часу розробника - це те, що ви маєте зараз.
DeadMG

9

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

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

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

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

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


+1 за розумну, проміжну пропозицію щодо вирішення існуючого завдання.

Так, саме такий підхід я досліджую. Ви піднімаєте хороші бали щодо продуктивності.
concurrency

Змінення речей, що проходять через одну диспетчерську нитку, не здається швидким виправленням, а скоріше для мене масовим рефактором.
Себастьян Редл

8

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

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


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

7

Якщо у вас є якийсь час, щоб присвятити рефакторинг вашої заявки, я б радив вам поглянути на модель актора (див., Наприклад, Theron , Casablanca , libcppa , CAF для реалізації C ++).

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

  1. Отримати повідомлення
  2. Виконайте обчислення
  3. Відправити повідомлення / створити / вбити інших акторів.

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

Я використовую (спрощену версію) цю модель у своєму проекті вже кілька місяців, і я вражений тим, наскільки вона міцна.


1
Бібліотека Akka для Scala - це приємна реалізація цього, що багато думає про те, як вбити батьківських акторів, коли діти вмирають, або навпаки. Я знаю, що це не C ++, але варто подивитися: akka.io
GlenPeterson

1
@GlenPeterson: Дякую, я знаю про akka (який я вважаю найцікавішим рішенням на даний момент, і працює як з Java, так і з Scala), але питання стосується C ++. Інакше можна розглянути питання про Ерланг. Я думаю, що в Ерланге всі головні болі багатогранного програмування пішли назавжди. Але, можливо, такі рамки, як акка, дуже близькі.
Джорджіо

"Я думаю, що в Ерланге всі головні болі багатопотокового програмування пішли назавжди". Я думаю, можливо, це трохи завищено. Або якщо це правда, то продуктивності може бути недостатньо. Я знаю, що Akka не працює з C ++, просто кажучи, що це виглядає як найсучасніший для управління кількома потоками. Однак це не є безпечним для ниток. Ви все ще можете пройти змінний стан між акторами і застрелити себе в ногу.
GlenPeterson

Я не експерт Ерланг, але AFAIK кожен актор виконується ізольовано і обмінюються незмінні повідомлення. Тож вам взагалі не доведеться мати справу з потоками та загальним змінним станом. Продуктивність, ймовірно, нижча за C ++, але це завжди відбувається, коли ви підвищуєте рівень абстракції (ви збільшуєте час виконання, але скорочуєте час розробки).
Джорджіо

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

6

Поширена помилка №1 - Виправлення проблеми одночасності, просто поставивши блокування навколо спільних даних, але забувши про те, що відбувається, коли методи не викликаються в очікуваному порядку. Ось дуже простий приклад:

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

Вам слід спробувати адаптувати модель / повідомлення повідомлень актора до певної міри та мати окремий стурбованість. Роль Fooявно полягає в обробці якогось HTTP-зв’язку. Якщо ви хочете спроектувати свою систему паралельно, саме вищевказаний шар повинен обробляти життєві цикли об'єктів і відповідно синхронізувати доступ.

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


2

Ваші проблеми досить погані, але типові для поганого використання C ++. Перегляд коду виправить деякі з цих проблем. 30 хвилин, один набір очних яблук збиває 90% результатів. (Цитата для цього є гугл)

№1 Проблема Вам потрібно переконатися, що існує чітка ієрархія блокування, щоб запобігти тупиковість блокування.

Якщо ви заміните функцію Autolock на обгортку та макрос, ви можете це зробити.

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

Вам також знадобиться статичний графік домінатора.

Тепер всередині блокування ви повинні оновити графік домінатора, і якщо ви отримаєте замовлення зміни, ви стверджуєте про помилку і скасовуєте.

Після обширного тестування ви можете позбутися більшості прихованих тупиків.

Код залишається як вправа для студента.

Проблема №2 піде (в основному)

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

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

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

  • Зміни даних, які перетинають потоки, входять у безпечну для потоків чергу, обробляються одним потоком. Робіть підписки. Тепер ви можете розібратися в причині потоків даних.

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

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

Нарешті, ваша проблема із закритими об’єктами.

Під час підрахунку довідок ви вирішили 50%. Інші 50% - це відкликати кількість зворотних дзвінків. Передайте власникам зворотного дзвінка відзив. Після виклику після вимкнення потрібно чекати нульового рахунку на знижку. Не вирішує складні графіки об'єктів; це потрапляння у справжній збір сміття. (Яка мотивація ins Java, щоб не давати обіцянок щодо того, коли або якщо буде завершено () буде викликано; щоб вийти з програмування таким чином.)


2

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

Університет Кенту має реалізацію C ++ ( https://www.cs.kent.ac.uk/projects/ofa/c++csp/ , клоновану за адресою https://github.com/themasterchef/cppcsp2 ).


1

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

В даний час я читаю це, і це пояснює всі проблеми, які ви можете отримати, і як їх уникнути, в C ++ (використовуючи нову бібліотеку для нарізки, але я думаю, що глобальні пояснення справедливі для вашого випадку): http: //www.amazon. com / C-Concurrency-Action-Practical-Multithreading / dp / 1933988770 / ref = sr_1_1? тобто = UTF8 & qid = 1337934534 & sr = 8-1

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

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

Навчання своєї команди розробників з питань багатопотокового коду.

Книга допомогла б, але я думаю, що вправи / прототипування та досвідчений наставник будуть краще.

Що б ти зробив?

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


Дякуємо за пропозицію книги. Я, швидше за все, підберу.
concurrency

Різьблення ниток справді важке. Не кожен програміст вирішує проблему. У діловому світі щоразу, коли я бачив використовувані нитки, вони були оточені замками таким чином, що жодна нитка не могла запускатись одночасно. Є правила, яких ви можете дотримуватися, щоб було простіше, але це все ще важко.
GlenPeterson

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

Я був дуже вражений Scala, спеціально для того, щоб принести переваги функціональному програмуванню незмінності, мінімальних побічних ефектів для Java, яка є прямим нащадком C ++. Він працює на віртуальній машині Java, тому може не мати необхідної продуктивності. Книга Джошуа Блоха "Ефективна Java" - це зведення до мінімуму змінності, створення герметичних інтерфейсів та безпеки потоку. Незважаючи на те, що на базі Java, я думаю, що ви можете застосувати 80-90% до C ++. Запитання щодо змінності та спільного стану (або змінності стану спільного стану) в оглядах коду може стати для вас хорошим першим кроком.
GlenPeterson

1

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

  • Сядьте і спроектуйте модель для нарізки для вашої програми. Це документ, який відповідає на такі питання: Які типи потоків у вас є? Які речі слід робити в якій нитці? Які різні типи моделей синхронізації слід використовувати? Іншими словами, він повинен описувати "правила взаємодії" при боротьбі з багатопотоковими проблемами.
  • Використовуйте інструменти аналізу потоків, щоб перевірити вашу кодову базу на наявність помилок. У Valgrind є перевірка ниток під назвою Helgrind яка добре такі речі, як спільний стан, що маніпулюють без належної синхронізації. Там, звичайно, є інші хороші інструменти, іди шукати їх.
  • Подумайте про перехід із C ++. C ++ - це кошмар, щоб писати паралельні програми. Моїм особистим вибором був би Ерланг , але це питання смаку.

8
Однозначно -1 за останній шматочок. Здається, що в коді ОП використовуються найпримітивніші інструменти, а не фактичні інструменти C ++.
DeadMG

2
Я не згоден. Паралельність C ++ - це кошмар, навіть якщо ви використовуєте належні C ++ механізми та інструменти. І будь ласка, зауважте, що я обрав формулювання " врахувати ". Я повністю розумію, що це може бути не реалістичною альтернативою, але залишатися з C ++ без розгляду альтернатив - просто нерозумно.
JesperE

4
@JesperE - вибач, але ні. Паралельність в C ++ - це лише кошмар, якщо ви зробите це одним із занадто низьких рівнів. Використовуйте правильну абстракцію різьблення, і це не гірше, ніж будь-яка інша мова чи час виконання. А при правильній структурі додатків це насправді так само просто, як і все, що я бачив.
Майкл Коне

2
Де я працюю, я вважаю, що ми маємо належну структуру додатків, використовуємо правильні абстракції для різьблення тощо. Незважаючи на це, ми протягом багатьох років витрачали налагодження помилок, які просто не з’являться на мовах, належним чином призначених для одночасності. Але я маю відчуття, що нам доведеться погодитися з цим не погоджуватися.
JesperE

1
@JesperE: Я згоден з тобою. Модель Erlang (для якої існують реалізації для Scala / Java, Ruby і, наскільки я знаю, також для C ++) набагато надійніша, ніж кодування безпосередньо з потоками.
Джорджо

1

Дивлячись на ваш приклад: Як тільки Foo :: Shutdown починає виконувати, виклик OnHttpRequestComplete не повинен бути більше можливим. Це не має нічого спільного з будь-якою реалізацією, вона просто не може працювати.

Ви також можете стверджувати, що Foo :: Shutdown не повинен викликатись, поки виклик OnHttpRequestComplete працює (безумовно, вірно), і, мабуть, ні, якщо виклик OnHttpRequestComplete все ще залишається недоступним.

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

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

Що вам потрібно зробити: Додайте специфікації до своїх функцій, точно кажучи , що вони будуть робити. (Наприклад, запуск http-запиту може виявитися невдалим після виклику Hase Shutdown). А потім запишіть свої функції, щоб вони відповідали специфікаціям.

Замки найкраще використовувати лише для найменшого можливого часу, щоб контролювати модифікацію загальних змінних. Таким чином, у вас може бути змінна "PerformShutDown", яка захищена блокуванням.


0

Що б ти зробив?

Чесно кажучи; Я втекла, швидко.

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

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

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

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