Concurrent write with OCaml Async
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
With OCaml Async, concurrent writes are usually safe only when you define a clear serialization point. Async is cooperative, not magically thread-safe, so if multiple jobs write to the same file or socket, you should decide explicitly whether those writes must be ordered, buffered, or coordinated through a single writer loop.
Async Concurrency Is Cooperative
Async concurrency is built around Deferred.t values and a single-threaded scheduler. Multiple jobs can be pending at once, but they run cooperatively rather than preemptively. That means shared resources still need structure.
For writing, the key questions are:
- are multiple jobs writing to the same destination
- must writes stay in order
- do you need to wait until bytes are flushed
If you skip those questions, “concurrent write” quickly turns into interleaved or poorly timed output.
Sequential Writes to One Writer.t
If order matters, the safest pattern is to keep one writer and serialize operations:
This is not “parallel,” but it is often the correct answer because the destination is inherently sequential.
A Queue Is Better Than Many Call Sites Writing Directly
If many concurrent jobs want to write to the same destination, a queue or pipe is cleaner than letting all of them touch the writer directly:
This pattern keeps concurrency in the producers while preserving a single ordered write path.
Distinguish Buffering from Flushing
Writer.write and Writer.write_line enqueue bytes into Async’s writer buffer. They do not mean the bytes are already on disk or on the socket. If you need durability or network ordering guarantees, wait for:
That is one of the most important semantics in the whole topic. A write call schedules output; flushed tells you the buffered data has actually been handed off more fully.
Writing to Different Files Is Easier
If each concurrent job writes to its own file or its own connection, coordination is much simpler because there is no shared destination:
In that case you can run many writes concurrently with Deferred.all_unit, because each writer owns its own resource.
Common Pitfalls
The most common mistake is assuming that because Async supports many concurrent jobs, multiple jobs should all write to the same writer independently. Without a serialization strategy, ordering becomes hard to reason about.
Another pitfall is forgetting the difference between buffered and flushed writes. If the program exits or closes too early, queued data may not be written when you think it is.
It is also easy to overcomplicate the design. If the destination is one file and order matters, a single writer loop is usually the simplest correct solution.
Finally, do not confuse Async concurrency with OS-threaded parallel writes. Async makes I/O composition cleaner, but shared output resources still need explicit coordination.
Summary
- In Async, concurrent producers should usually feed a single serialized writer for one shared destination.
- Use
Writer.writeorWriter.write_lineto enqueue bytes andWriter.flushedwhen completion matters. - A
Pipeis a good coordination mechanism when many jobs need to submit writes. - Writing to separate files or sockets is easier because each resource can be owned independently.
- The right design depends more on ordering and flush requirements than on the word “concurrent.”

