VOOZH about

URL: https://dev.to/sommukhopadhyay/half-sync-half-async-design-pattern-implemented-using-julia-1cp8

⇱ Half Sync - Half Async design pattern implemented using Julia... - DEV Community


My deep study of the Android AsyncTask framework many years ago - a perfect example of this design pattern by bright Google engineers...

πŸ‘ Half Sync - Half Async
Half Sync - Half Async

πŸ‘ Half Sync - Half Async Pattern

# The Julia Code
struct Task
 id::Int
 payload::Float64
end

function worker(id, ch::Channel)
 for task in ch
 println("Worker $id processing Task $(task.id)")
 result = sum(sin.(1:10^6 .* task.payload))
 println("Worker $id finished Task $(task.id)")
 end
 println("Worker $id shutting down")
end

function async_producer(ch::Channel, n::Int)
 for i in 1:n
 sleep(rand())
 println("Producing Task $i")
 put!(ch, Task(i, rand()))
 end
 close(ch)
end

function run_system(num_tasks=10, num_workers=4)
 ch = Channel{Task}(32)

 @sync begin
 # Producer runs as async task tracked by @sync
 @async async_producer(ch, num_tasks)

 # Workers
 for i in 1:num_workers
 Threads.@spawn worker(i, ch)
 end
 end
end

run_system(20, Threads.nthreads())

1. The Pattern Refresher

Half-Sync/Half-Async splits a system into:

πŸ”Ή Async Layer

  • Non-blocking

  • Event-driven

  • Produces work

πŸ”Ή Sync Layer

  • Blocking / CPU-bound

  • Deterministic execution

  • Processes work

πŸ”Ή Boundary

  • A queue (here: Channel)

  • Decouples the two layers

2. Mapping The Code to the Pattern

πŸ”Έ (A) Boundary β†’ Channel

ch = Channel{Task}(32)

This is the core of the pattern.

πŸ‘‰ It acts as:

  • A thread-safe queue

  • A decoupling buffer

  • A synchronization boundary

Interpretation:

β€œAsync world hands off work to Sync world through a controlled interface.”

(B) Async Layer β†’ async_producer

@async async_producer(ch, num_tasks)

Inside:

for i in 1:n
 sleep(rand())
 put!(ch, Task(i, rand()))
end
close(ch)

Why this is β€œAsync”:

  • @async β†’ cooperative scheduling (non-blocking)

  • sleep(rand()) β†’ simulates unpredictable external events

  • put! β†’ hands off work without doing computation

Conceptual role:

β€œI don’t process. I just observe and emit events.”

(C) Sync Layer β†’ worker

Threads.@spawn worker(i, ch)

Inside:

for task in ch
 result = sum(sin.(1:10^6 .* task.payload))
end

Why this is β€œSync”:

  • take! (via for task in ch) β†’ blocking

  • CPU-heavy computation

  • Runs on real OS threads

Conceptual role:

β€œGive me work. I will process it fully and deterministically.”

(D) Coordination β†’ @sync

@sync begin
 @async async_producer(...)
 Threads.@spawn worker(...)
end

This is not part of the original pattern per se, but in Julia it ensures:

  • The system behaves like a long-running service

  • Main thread waits for both layers

3. End-to-End Flow (Pattern in Action)

Step-by-step:

  1. Async Layer wakes up

  2. Task is enqueued

  3. Sync Layer pulls work

  4. Processing happens

  5. Repeat until channel closes

4. Why This is Half-Sync/Half-Async (Not Just Threads)

Because of strict separation of concerns :

Concern Where handled
Event timing Async layer
Work queueing Channel
Execution Sync layer

πŸ‘‰ The producer never processes

πŸ‘‰ The worker never generates events

That separation is the essence of the pattern

5. Key Properties The Code Achieves

Decoupling

  • Producer speed β‰  Worker speed

  • Buffered via Channel(32)

Backpressure

  • If workers are slow β†’ channel fills β†’ put! blocks

  • Natural flow control

Scalability

Threads.@spawn worker(i, ch)

  • Increase workers β†’ parallelism increases

Clean Shutdown

close(ch)

  • Workers exit automatically via:
for task in ch

6. Subtle but Deep Insight

The system is not just parallel β€” it is:

A streaming system with a controlled execution boundary

This is exactly how:

  • High-performance servers

  • Simulation engines

  • Data pipelines

are designed internally.