跪拜 Guibai
← Back to the summary

Coroutines Don't Beat Threads on Speed — They Beat Asynchronous Complexity

Many people think coroutines are more efficient than threads because:

How should I put this... it's complicated.

Actually, these are not the core reasons why Kotlin coroutines changed Android development.

Because:

Thread pools can also reuse threads;

Netty can also handle massive numbers of connections;

Node.js achieved single-threaded high concurrency long ago.

The real efficiency of coroutines lies in:

And these are the things that truly changed the way we do asynchronous development.


1. The biggest cost of the thread era was actually asynchronous complexity

Many people think the biggest cost of the thread model is:

Blocking.

But for the vast majority of Android developers, the real pain point is:

Asynchrony shatters program control flow.

Take the classic callback pattern:

loginAsync { user ->
    queryProfileAsync(user.id) { profile ->
        queryVipAsync(profile.id) { vip ->
            updateUi(vip)
        }
    }
}

The business logic is actually very simple:

Login
↓
Query user info
↓
Query membership info
↓
Update UI

But the code structure becomes:

callback
 └── callback
      └── callback
           └── callback

And then various problems start to emerge:

What really drains development efficiency is never the threads themselves,

but:

Asynchronous code loses the ability for linear expression.


2. Writing synchronous-style code in the thread era often required various JUC tools

Faced with Callback Hell, a natural thought arises:

Can we rewrite asynchronous code back into synchronous code?

In the thread era, you certainly could.

But it usually required various synchronization tools.

For example:

CountDownLatch latch = new CountDownLatch(1);

loginAsync(user -> {
    result = user;
    latch.countDown();
});

latch.await();

User user = result;

Or:

User user = future.get();

Or:

User user = blockingQueue.take();

The essence is always:

Async Callback
        │
        ▼
JUC Synchronizer (CountDownLatch, Future, BlockingQueue...)
        │
        ▼
Thread blocking wait
        │
        ▼
Callback notifies thread to continue execution

The code does indeed become:

User user = login();
Profile profile = queryProfile(user.id);
Vip vip = queryVip(profile.id);

updateUi(vip);

But the cost is also very obvious.

To get the readability of synchronous code, we have to:

So projects start to see more and more of:

Very often:

These tools are not what the business logic actually needs.

Their sole purpose is just:

To stitch asynchronous callbacks back together into linear code.

More importantly, these synchronization tools themselves carry extra costs.

For example:

That is to say:

In the thread era, to get synchronous code, you paid not only the cost of thread blocking, but also the cost of massive synchronizer coordination.


3. The greatest value of coroutines: Synchronizing asynchrony, non-blockingly

And coroutines solved this problem elegantly for the first time.

val user = login()
val profile = queryProfile(user.id)
val vip = queryVip(profile.id)

updateUi(vip)

It looks like synchronous code:

A → B → C → D

But the actual execution process is:

login()
↓
Suspend coroutine
↓
Release thread
↓
Wait for network response
↓
Resume coroutine
↓
Continue executing the next line of code

Throughout the entire process:

There is no:

Not even:

Thread.sleep()

Because coroutines do not rely on these synchronization tools to wait for results.

The essence of suspend is the compiler-generated Continuation + State Machine.

When an asynchronous task is not complete:

Save execution context
↓
Suspend coroutine
↓
Release thread
↓
Async task completes
↓
resume()
↓
Restore state machine and continue execution

What is waiting is:

The coroutine

Not:

The thread

Therefore:

Coroutines did not turn Callbacks into CountDownLatches, but directly turned Callbacks into Continuations.

The entire asynchronous flow returns to linear expression.

Coroutines achieved something that was very hard to do elegantly in the thread era:

Giving asynchronous code the readability of synchronous code.

While simultaneously maintaining:

Non-blocking execution.

This is perhaps the greatest value of Kotlin coroutines compared to the traditional thread model.

Because it truly realized for the first time:

Linear expression + Non-blocking execution.


4. Coroutines not only reduce lock contention, but also reduce the use of synchronizers

Under the thread model:

When multiple threads access shared resources simultaneously:

are almost unavoidable.

Not only that.

To reorganize asynchrony back into a synchronous flow, you also need:

That is to say:

A large amount of code in the thread era is actually centered around:

How threads coordinate with each other.

Rather than:

The business logic itself.

Coroutines change this.

For example:

val mutex = Mutex()

mutex.withLock {
    updateData()
}

If the lock is already held:

Suspend coroutine
↓
Release thread
↓
Wait to resume

What is waiting is:

The coroutine

Not:

The thread

Therefore:

However, the truly powerful thing about coroutines is not that Mutex is faster than synchronized.

It is:

Many problems that originally required locking simply don't need locking under the coroutine model.

For example:

Multiple Coroutines
      │
      ▼
 Channel
      │
      ▼
 Single Consumer
      │
      ▼
 Modify Shared State

Or:

StateFlow

SharedFlow

Actor

Shared state gradually becomes:

Single Owner

Multiple tasks:

Collaborate through message passing.

Rather than:

Multiple threads competing for the same lock.

Thus, the Kotlin world increasingly favors:

Over:

Because:

The coroutine era emphasizes collaboration, not competition.

What is truly reduced is not just lock contention.

More importantly:

Many problems that previously had to be solved with locks and synchronizers simply don't need to exist under the coroutine model.


5. Structured concurrency further reduces complexity

In the thread era:

Task lifecycles and business lifecycles were often separate.

For example:

The page is destroyed,
but the thread is still executing.

So you often see:

With coroutines:

ViewModelScope
    ├── task1
    ├── task2
    └── task3

When the parent coroutine is cancelled:

All child coroutines are automatically cancelled.

Lifecycles are naturally bound.

A large number of synchronization and state management problems thus disappear.

This is another easily overlooked advantage of Kotlin coroutines:

Structured concurrency reduces system complexity.


6. Coroutines did not eliminate callbacks

Many people have a feeling when they first encounter coroutines:

Suspend functions have no callbacks.

Actually, they do.

If you decompile a suspend function:

suspend fun login(): User

It will ultimately roughly become:

Object login(Continuation<? super User> continuation)

The essence is still:

Request completes
↓
Callback to Continuation
↓
Resume coroutine execution

The only difference is:

Before:

Developer maintains the callback chain.
Developer maintains the state machine.

Now:

Compiler maintains the callback chain.
Compiler maintains the state machine.

So in a sense:

Coroutines did not eliminate callbacks.

They just:

Hid the callbacks inside the compiler and runtime.


7. What coroutines truly optimize is not threads, but asynchronous programming

Many articles like to say:

Coroutines are faster than threads.

Actually, this is not accurate.

It is always threads that truly execute the code.

What coroutines optimize is never:

CPU computation speed

But:

The organizational cost of asynchronous programs

What they reduce is:

Therefore, a more accurate statement would be:

Coroutines do not make programs run faster.

Rather:

They make asynchronous programs simpler, more stable, and easier to maintain.


8. Summary

Thread pools solve:

The problem of thread reuse.

Coroutines solve:

The problem of asynchronous expression.

Therefore:

Coroutines are more efficient than threads not because they are lighter to create, nor because they use fewer threads.

What truly changed is:

They redesigned the way asynchronous programs are organized.

They turned:

So, what coroutines truly optimize is never the CPU, nor threads.

It is:

The organizational cost of asynchronous programs.

They allow developers to stop wasting massive amounts of energy on:

And instead refocus their attention on:

The business logic itself.

So, I believe the truly great thing about Kotlin coroutines is not that they are some percentage faster than threads.

It is that they:

Let you write asynchronous programs with a synchronous mindset.

Use a non-blocking suspension mechanism to achieve synchronous-code-like readability.

Coroutines do not eliminate all asynchronous overhead, but reduce the massive synchronization control cost introduced just to make asynchronous flows behave like synchronous code.


What coroutines reduce is not the number of Threads, but:

So essentially, the reason coroutines are more efficient than threads is that they reduce asynchronous complexity.

References

Why does the Kotlin official website no longer emphasize "coroutines are lightweight threads"?

From Callback to Coroutines: The Evolution of Android Async Concurrency Solutions

The word "Structured" essentially means—turning chaotic things into organized, rule-based, bounded things

From "Caller's Treading on Thin Ice" to "Interface's Natural Semantics": Lessons from Room/DataStore/Retrofit

Why modern Android officially recommends Repository expose suspend fun instead of launching internally

# Android Architecture Guide: Stop exposing start/stop in the Data layer; use Flow to manage lifecycles

Stop launch(IO): 3 Hidden Anti-patterns of Coroutine Thread Switching

Understanding Android Clean Architecture from Food Delivery: Why the Boss Doesn't Need to Know What Car the Delivery Guy Drives

Getting Started with Android Clean Architecture with a Small Demo

Modern Android Architecture Doesn't Need an Event Bus

Why I Don't Handle Exceptions Directly in Android ViewModel?

Comments

Top 2 from juejin.cn, machine-translated. The original thread is authoritative.

用户581914407356

Linear expression is the core.

用户27910156679

The final summary is not bad.