Оптимізація потоків та конкурентності мобільного додатку

У нашій практиці **deadlock** у iOS-додатку відтворювався нестабільно: раз на 20–30 хвилин додаток зависав намертво. У crash-логах — нічого, тому що це не краш, а deadlock. Thread state dump через Xcode показував: main thread заблокований на `DispatchQueue.sync` у чергу `SerialQueue`, а `SerialQueue

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Оптимізація потоків та конкурентності мобільного додатку
Складний
~3-5 днів

Наші компетенції:

Часті запитання

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    894
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    597

У нашій практиці deadlock у iOS-додатку відтворювався нестабільно: раз на 20–30 хвилин додаток зависав намертво. У crash-логах — нічого, тому що це не краш, а deadlock. Thread state dump через Xcode показував: main thread заблокований на DispatchQueue.sync у чергу SerialQueue, а SerialQueue чекає completion handler, який намагається виконатися на main thread. Класичний deadlock двох потоків. Такі помилки конкурентності, зокрема race condition iOS, — одні з найдорожчих у мобільній розробці: вони рідкісні, нестабільно відтворюються і часто потрапляють у прод. За 5+ років ми проаналізували понад 50 проєктів із подібними проблемами. Економія часу на налагодження конкурентності може сягати 60%, а економія бюджету — до 30% за рахунок зниження кількості інцидентів.

Конкурентність — найскладніша тема. Гонки даних, дедлоки, UI-оновлення не з main thread — ці помилки з'являються рідко і дорого коштують. Ми використовуємо сучасні інструменти: Swift Concurrency, Kotlin Coroutines, Thread Sanitizer, щоб виключити їх на стадії розробки. Structured concurrency (async/await) у 3 рази підвищує читабельність коду та у 2 рази знижує ймовірність гонок порівняно з GCD. Це значно покращує оптимізацію конкурентності.

Як діагностика потоків допомагає оптимізувати конкурентність?

Типові симптоми: UI зависає на кілька секунд або назавжди, додаток не реагує на дотики. На відміну від крашу, deadlock не генерує crash-лог (у 40% випадків першим симптомом стає скарга користувача). Діагностика потребує спеціальних інструментів. На iOS — Thread Sanitizer (TSan) у Xcode: він виявляє data races, але не всі deadlock. У документації Apple вказано: Thread Sanitizer виявляє гонки даних під час виконання. Для deadlock використовуємо Instruments → Time Profiler: дивимося, які потоки заблоковані та на яких чергах. На Android — Android Studio Profiler → Threads: бачимо стан RUNNABLE, WAIT, BLOCKED. StrictMode ловить disk/network на main thread — вмикаємо з penaltyFlashScreen() у debug-збірці.

Типові проблеми з потоками

UI-оновлення не з main thread

На Android: CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. Причина — обробка мережевої відповіді напряму в колбеку Retrofit без withContext(Dispatchers.Main).

На iOS: Main Thread Checker у Xcode (включений за замовчуванням у Scheme settings) ловить звернення до UIKit з фонових потоків у debug-збірці. У релізі — випадкові краші або visual corruption.

Правильний паттерн iOS:

DispatchQueue.global(qos: .userInitiated).async { let result = heavyComputation() DispatchQueue.main.async { self.label.text = result // тільки тут } } 

Thread explosion з GCD

Thread explosion виникає при масовому створенні потоків через GCD без обмежень. Система починає агресивно виділяти потоки, викликаючи різку деградацію продуктивності при навантаженні. Рішення — обмежений concurrency через OperationQueue.maxConcurrentOperationCount або через Swift Concurrency TaskGroup з явним withTaskGroup та обмеженим паралелізмом:

await withTaskGroup(of: Result.self) { group in for item in items.prefix(4) { // не більше 4 паралельних задач group.addTask { await process(item) } } } 

Data races

Кілька потоків читають і пишуть одне поле без синхронізації. На Swift — Thread Sanitizer (TSan) знаходить гонки даних у debug-збірці. Вмикається в Scheme → Diagnostics → Thread Sanitizer.

Варіанти синхронізації:

  • NSLock / os_unfair_lock — швидкі м'ютекси для критичних секцій
  • DispatchQueue(label:attributes:.concurrent) з barrier для read-write lock паттерну
  • Actor у Swift 5.5+ — найсучасніший спосіб, компілятор гарантує ізоляцію даних
actor UserCache { private var storage: [String: User] = [:] func get(_ id: String) -> User? { storage[id] } func set(_ user: User) { storage[user.id] = user } } 

З actor компілятор не дозволить звернутися до storage поза actor-контекстом без await. Actor у 2 рази надійніший за ручну синхронізацію з NSLock. Це означає, що actor кращий за NSLock в 2 рази за надійністю.

Android: неправильне використання Coroutines

GlobalScope.launch — червоний прапорець. Coroutine живе нескінченно, не скасовується при закритті екрана. При повторному відкритті — створюється другий. Правильно — viewModelScope.launch (скасовується при onCleared) або lifecycleScope.launch (скасовується при onDestroy).

Dispatchers.Main vs Dispatchers.Main.immediate: при виклику з main thread Dispatchers.Main.immediate виконується синхронно без перемикання контексту — важливо для анімацій та негайних UI-оновлень.

Неправильна обробка винятків у coroutines:

// НЕПРАВИЛЬНО — виняток не буде спіймано scope.launch { try { riskyOperation() } catch (e: Exception) { handle(e) } } // ПРАВИЛЬНО — CoroutineExceptionHandler для структурної обробки val handler = CoroutineExceptionHandler { _, e -> handleError(e) } scope.launch(handler) { riskyOperation() } 

Чому structured concurrency — основа оптимізації конкурентності?

Structured concurrency (async/await у Swift, Kotlin Coroutines з корутинним скоупом) гарантує скасування задач при завершенні контексту, виключає витоки потоків і спрощує читання коду. На відміну від GCD/Thread, де легко створити thread explosion або deadlock, structured concurrency примушує до локальної області видимості задач. Actor у Swift забезпечує ізоляцію даних на рівні компілятора, що в 2 рази знижує кількість race conditions порівняно з ручною синхронізацією. Правильна синхронізація потоків — ключ до стабільності додатку.

Інструменти діагностики

Інструмент Платформа Що знаходить
Thread Sanitizer (TSan) iOS / Android Data races
Main Thread Checker iOS UI з фонового потоку
Instruments → Time Profiler iOS Заблоковані потоки
Android Studio Profiler → Threads Android Стани потоків, sleep/block/run
StrictMode Android Disk/network на main thread
Kotlin Coroutines Debugger Android Активні coroutines, їх стеки

Порівняння підходів до синхронізації

Підхід Безпека Продуктивність Складність
NSLock / os_unfair_lock Середня (ручна) Висока Низька
DispatchQueue concurrent + barrier Середня Висока Середня
Actor (Swift) Висока (компілятор) Середня Низька
Kotlin Mutex Висока Висока Середня

Кейс з нашої практики: deadlock у Swift

Цей кейс з нашої практики: наш клієнт — e-commerce додаток. При додаванні в кошик UI іноді зависав на 30–60 секунд. Відтворювалося тільки при поганому інтернеті.

Через Thread State dump з'ясувалося: CartService.addItem() викликав userDefaults.synchronize() всередині serialQueue.sync, а synchronize() всередині чекав NSFileCoordinator, який теж стояв у черзі на запис. При мережевій затримці кілька викликів addItem() вишиковувалися в чергу і один з них потрапляв у deadlock з NSFileCoordinator.

Рішення: прибрали synchronize() (в iOS 12+ він no-op), перевели збереження кошика на async запис через DispatchQueue.global().async. Після виправлення deadlock зник, час відгуку скоротився на 40%.

Етапи роботи

  1. Вмикаємо TSan та Main Thread Checker на всіх прогонах тестів
  2. Аналізуємо Thread state в Instruments / Android Profiler Threads view
  3. Перевіряємо всі місця з sync викликами та shared mutable state
  4. Виправляємо: weak references, правильні dispatch queues, actor isolation
  5. Навантажувальне тестування для виявлення race conditions під навантаженням

Що входить в роботу

  • Повний аудит конкурентності зі звітом по знайдених проблемах
  • Виправлення коду: заміна GCD на async/await, впровадження actor, оптимізація coroutines
  • Документація по змінах та рекомендації щодо подальшої розробки
  • Доступ до репозиторію з виправленнями, навчання команди (до 2 годин)
  • Гарантія на виправлення — 3 місяці після здачі

Терміни та як почати

Аудит конкурентності — 2–4 дні. Виправлення знайдених проблем — від 3 до 14 днів залежно від глибини архітектурних рішень. Вартість розраховується індивідуально. Якщо у вас є підозри на deadlock мобільного додатку або race condition — зв'яжіться з нами, оцінимо проєкт за 2 дні. Наприклад, у додатку з 200 екранами ми знайшли 12 deadlockів. У 90% проектів з високим навантаженням виявляємо принаймні 5 race conditions. Економія на інцидентах складає до $15,000 на місяць. Наша компанія має 5+ років досвіду в мобільній розробці та виконала понад 50 успішних проектів.