That runs into a problem on a quick reply. Process A does the write on pipe AB, then gives up the CPU to B. A is still in ready to run state. B quickly generates a reply and writes it to pipe BA. But there's no read pending on pipe BA yet. So both processes are now in ready to run state, contending for the CPU, along with anything else that needed it.
There's an alternative - unbuffered pipes. This is like a Go channel of length 0 - all writes block until the read empties the channel. This is better from a CPU dispatching perspective, in that a write to a channel implies an immediate transfer of control to the receiver. Of course, Go is doing this in one address space, not across a protection boundary.
The QNX approach worked well for service-type requests. You could set up a service usable by multiple processes. Each request contained the info needed to send the reply back to the caller. The service didn't have to open a pipe to the requestor. This even understood process priority, so high priority requests were serviced first, an essential feature in a hard real time system.
Read the second sentence; process A does the write+select on AB and [BA,...], yields to B, is not ready to run; B generates a reply and writes it to BA, which has a read (technically select) already pending.
That's the advantage of a combined read and write. But if you're going to have that, it's more convenient to explicitly package it as a request/reply. Less trouble with things like having two messages in the pipe and such.
You might be waiting (via select) for multiple replies; you don't know whether B will (say) grab something out of disk cache and chuck it back to you, or if it'll fire off a seek command to the spinning rust and take a nap just as your network round-trip completes. Having two (or more) messages in (separate) pipes is the point of using select.
There's an alternative - unbuffered pipes. This is like a Go channel of length 0 - all writes block until the read empties the channel. This is better from a CPU dispatching perspective, in that a write to a channel implies an immediate transfer of control to the receiver. Of course, Go is doing this in one address space, not across a protection boundary.
The QNX approach worked well for service-type requests. You could set up a service usable by multiple processes. Each request contained the info needed to send the reply back to the caller. The service didn't have to open a pipe to the requestor. This even understood process priority, so high priority requests were serviced first, an essential feature in a hard real time system.