Mobile App Thread & Concurrency Optimization

In our practice, a **deadlock** in an iOS app reproduced unreliably: once every 20–30 minutes the app would freeze completely. Crash logs showed nothing because it isn't a crash—it's a deadlock. Thread state dump via Xcode revealed: main thread blocked on `DispatchQueue.sync` to a `SerialQueue`, and

Development and support of all types of mobile applications:

Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1All 1734 services
Mobile App Thread & Concurrency Optimization
Complex
~3-5 days

Our competencies:

Frequently Asked Questions

Latest works

  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    894
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    597

In our practice, a deadlock in an iOS app reproduced unreliably: once every 20–30 minutes the app would freeze completely. Crash logs showed nothing because it isn't a crash—it's a deadlock. Thread state dump via Xcode revealed: main thread blocked on DispatchQueue.sync to a SerialQueue, and that queue was waiting for a completion handler that tried to execute on the main thread. Classic two-thread deadlock. Such concurrency bugs are among the costliest in mobile development: they are rare, reproduce sporadically, and often make it to production. Over 5 years, we have analyzed more than 50 projects with similar issues. Time savings on debugging concurrency can reach 60%, and budget savings up to 30% thanks to fewer incidents.

Concurrency is one of the hardest topics. Data races, deadlocks, UI updates not from the main thread—these bugs appear rarely and are expensive. We use modern tools: Swift Concurrency, Kotlin Coroutines, Thread Sanitizer to eliminate them at development stage. Structured concurrency (async/await) improves code readability by 3x and reduces the probability of races by 2x compared to GCD.

How does thread diagnostics help optimize concurrency?

Typical symptoms: UI freezes for seconds or forever, app doesn't respond to touches. Unlike a crash, a deadlock doesn't generate a crash log (in 40% of cases the first symptom is a user complaint). Diagnostics require special tools. On iOS — Thread Sanitizer (TSan) in Xcode: it detects data races but not all deadlocks. Apple documentation states: Thread Sanitizer detects data races during execution. For deadlocks we use Instruments → Time Profiler: see which threads are blocked and on which queues. On Android — Android Studio Profiler → Threads: view states RUNNABLE, WAIT, BLOCKED. StrictMode catches disk/network on main thread — we enable it with penaltyFlashScreen() in debug builds.

Typical threading problems

UI updates not from main thread

On Android: CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. Cause — handling a network response directly in a Retrofit callback without withContext(Dispatchers.Main).

On iOS: Main Thread Checker in Xcode (enabled by default in Scheme settings) catches UIKit accesses from background threads in debug builds. In release — random crashes or visual corruption.

Correct iOS pattern:

DispatchQueue.global(qos: .userInitiated).async { let result = heavyComputation() DispatchQueue.main.async { self.label.text = result // only here } } 

Thread explosion with GCD

Thread explosion occurs when many threads are created via GCD without limits. The system aggressively allocates threads, causing sharp performance degradation under load. The fix is limited concurrency via OperationQueue.maxConcurrentOperationCount or via Swift Concurrency TaskGroup with explicit withTaskGroup and limited parallelism:

await withTaskGroup(of: Result.self) { group in for item in items.prefix(4) { // no more than 4 parallel tasks group.addTask { await process(item) } } } 

Data races

Multiple threads read and write a field without synchronization. In Swift — Thread Sanitizer (TSan) detects data races in debug builds. Enable in Scheme → Diagnostics → Thread Sanitizer.

Synchronization options:

  • NSLock / os_unfair_lock — fast mutexes for critical sections
  • DispatchQueue(label:attributes:.concurrent) with barrier for read-write lock pattern
  • Actor in Swift 5.5+ — the most modern approach, compiler guarantees data isolation
actor UserCache { private var storage: [String: User] = [:] func get(_ id: String) -> User? { storage[id] } func set(_ user: User) { storage[user.id] = user } } 

With an actor, the compiler won't allow access to storage outside the actor context without await. Actor is 2x more reliable than manual synchronization with NSLock.

Android: improper use of Coroutines

GlobalScope.launch is a red flag. The coroutine lives forever, not cancelled when the screen closes. On re-open, a second one is created. Correct: viewModelScope.launch (cancelled on onCleared) or lifecycleScope.launch (cancelled on onDestroy).

Dispatchers.Main vs Dispatchers.Main.immediate: when called from main thread Dispatchers.Main.immediate executes synchronously without context switch — important for animations and immediate UI updates.

Incorrect exception handling in coroutines:

// WRONG — exception won't be caught scope.launch { try { riskyOperation() } catch (e: Exception) { handle(e) } } // CORRECT — CoroutineExceptionHandler for structural handling val handler = CoroutineExceptionHandler { _, e -> handleError(e) } scope.launch(handler) { riskyOperation() } 

Why structured concurrency is the foundation of concurrency optimization?

Structured concurrency (async/await in Swift, Kotlin Coroutines with coroutine scope) guarantees cancellation of tasks when the context finishes, eliminates thread leaks, and simplifies code reading. Unlike GCD/Thread, where thread explosion or deadlock is easy to create, structured concurrency enforces local task scope. Actor in Swift provides data isolation at compiler level, reducing race conditions by 2x compared to manual synchronization.

Diagnostic tools

Tool Platform What it finds
Thread Sanitizer (TSan) iOS / Android Data races
Main Thread Checker iOS UI from background thread
Instruments → Time Profiler iOS Blocked threads
Android Studio Profiler → Threads Android Thread states, sleep/block/run
StrictMode Android Disk/network on main thread
Kotlin Coroutines Debugger Android Active coroutines, their stacks

Synchronization approach comparison

Approach Safety Performance Complexity
NSLock / os_unfair_lock Medium (manual) High Low
DispatchQueue concurrent + barrier Medium High Medium
Actor (Swift) High (compiler) Medium Low
Kotlin Mutex High High Medium

Case from our practice: deadlock in Swift

An e-commerce client app: when adding to cart, the UI sometimes froze for 30–60 seconds. Reproduced only on poor internet.

Thread state dump revealed: CartService.addItem() called userDefaults.synchronize() inside serialQueue.sync, and synchronize() inside waited on NSFileCoordinator, which was also queued for writing. With network delay, multiple addItem() calls queued up and one ended up in a deadlock with NSFileCoordinator.

Solution: removed synchronize() (no-op in iOS 12+), moved cart saving to async write via DispatchQueue.global().async. After the fix, deadlock disappeared, response time improved by 40%.

Work stages

  1. Enable TSan and Main Thread Checker on all test runs
  2. Analyze thread state in Instruments / Android Profiler Threads view
  3. Check all places with sync calls and shared mutable state
  4. Fix: weak references, correct dispatch queues, actor isolation
  5. Load testing to detect race conditions under load

What's included

  • Full concurrency audit with report of found issues
  • Code fixes: replace GCD with async/await, introduce actor, optimize coroutines
  • Documentation of changes and recommendations for further development
  • Access to the fix repository, team training (up to 2 hours)
  • Warranty on fixes — 3 months after delivery

Timelines and how to start

Concurrency audit — 2–4 days. Fixing found issues — from 3 to 14 days, depending on the depth of architectural changes. Cost is calculated individually. If you suspect deadlocks or race conditions — contact us, we will evaluate your project in 2 days. Order a concurrency audit today and get a detailed report with fix proposals.