You are right to be worried about atomicity here.
If another process, or another thread within the same process, is also writing to the same communications channel, then
write(fd, "ABCD", 4);
will write the four bytes "ABCD" atomically, in the sense that output from the other writers cannot appear in the middle of those four bytes. On the other hand,
write(fd, "A", 1);
write(fd, "B", 1);
write(fd, "C", 1);
write(fd, "D", 1);
will write each byte separately, and output from the other writers can appear in between the letters.
However, atomicity is only guaranteed for short writes, where the third argument is smaller than the constant PIPE_BUF. If you write more data than that, there is no way to ensure that output from other writers doesn't appear in the middle. PIPE_BUF may be as small as 512 but is usually somewhat larger than that nowadays.
The easiest way to see this for yourself is with fork and writes to standard output:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
} else if (pid == 0) {
// child
write(1, "ABCD", 4); sleep(1);
write(1, "A", 1); sleep(1);
write(1, "B", 1); sleep(1);
write(1, "C", 1); sleep(1);
write(1, "D", 1);
return 0;
} else {
// parent
write(1, "1234", 4); sleep(1);
write(1, "1", 1); sleep(1);
write(1, "2", 1); sleep(1);
write(1, "3", 1); sleep(1);
write(1, "4", 1);
waitpid(pid, 0, 0);
write(1, "\n", 1);
return 0;
}
}
This program will print something like 1234ABCD1AB23CD4. The first ABCD will always appear as a group, and the first 1234 will also always appear as a group, but either one could be first; the second ABCD and 1234 might get mixed up together arbitrarily.
(Quiz question: why did I put the final write(1, "\n", 1) in the parent, after the waitpid?)