andersch.dev

<2022-05-04 Wed>

Asynchronous IO (async_io/asio)

In programming, the conventional form of handling Input/Output (IO) operations is to issue synchronous (or blocking) IO calls where the program has to wait for the IO operation to finish.

To avoid this, the program could open a thread solely for performing the IO operation, but this comes with typical multithreading issues like overhead and problems like race conditions. Additionally, the programming environment might not have threading capabilities - such as a browser running JavaScript. The alternative is to use asynchronous IO calls, typically provided by the operating system.

Using asynchronous IO makes the most sense when the program is IO-bound.

Example for synchronous IO

#define BUFFER_SIZE 13
char buf[BUFFER_SIZE] = {0};
FILE* fp = fopen("async_io.org", "r");
if (!fp) { /* handle error */ }
int ret = fread(buf, 1, BUFFER_SIZE, fp);
if (!ret) { /* read failed */ }
else      { printf("%s\n", buf); }
fclose(fp);

Example for asynchronous IO using aio.h in C

#define BUFFER_SIZE 13
char buf[BUFFER_SIZE];
FILE* fp = fopen("async_io.org", "r");
if (!fp) { /* handle error */ }

struct aiocb* aio = malloc(sizeof(struct aiocb)); // asynchronous IO control block
if (!aio) { /* handle failed malloc */ }

memset(aio, 0, sizeof(*aio));
aio->aio_buf    = buf;
aio->aio_fildes = fileno(fp);
aio->aio_nbytes = BUFFER_SIZE;
aio->aio_offset = 0;

int result = aio_read(aio);
if (result < 0) { /* read failed */ }

while(aio_error(aio) == EINPROGRESS) { /* perform operations while async IO is still busy */ }

int ret = aio_return(aio);
if (!ret) { /* read failed */ }
else      { printf("%s\n", buf); }
fclose(fp);