Prev: common-concurrency-problems Next: summary-dialogue-on-concurrency
Using threads for concurrency is difficult. Some other ways of having concurrency are outlined below:
An event loop is pretty straightforward. It takes events from a queue, and then processes them in a loop:
while (1) {
= getEvents();
events for (e in events) { processEvent(e); }
}
This works well for a single-threaded environment, and greatly simplifies programming (no more threads).
To build an event loop, select()
or poll()
can be used. Select looks like this, where a set of file descriptors can
be checked if they are ready to read/write/error to in the future.
int select(int nfds,
*restrict readfds,
fd_set *restrict writefds,
fd_set *restrict errorfds,
fd_set struct timeval *restrict timeout);
Some example code using select follows:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(void) {
// open and set up a bunch of sockets (not shown)
// main loop
while (1) {
// initialize the fd_set to all zero
;
fd_set readFDs(&readFDs);
FD_ZERO
// now set the bits for the descriptors
// this server is interested in
// (for simplicity, all of them from min to max)
int fd;
for (fd = minFD; fd < maxFD; fd++)
(fd, &readFDs);
FD_SET
// do the select
int rc = select(maxFD + 1, &readFDs, NULL, NULL, NULL);
// check which actually have data using FD_ISSET()
int fd;
for (fd = minFD; fd < maxFD; fd++)
if (FD_ISSET(fd, &readFDs))
(fd);
processFD}
}
Unfortunately, event based systems don’t work well on systems with synchronous I/O (all reads and writes would be blocking, which wouldn’t perform well).
For this, there’s an Asynchronous I/O system call, like AIO.
The caller fills out the aiocb
struct:
struct aiocb {
int aio_fildes; // File descriptor
; // File offset
off_t aio_offsetvolatile void *aio_buf; // Location of buffer
size_t aio_nbytes; // Length of transfer
};
And calls aio_read(struct aiocb *aiocbp);
Which issues the I/O, which returns right away if successful and does I/O in the background.
The system can then poll the struct with
int aio_error(const struct aiocb *aiocbp)
.
Polling is difficult and ill performant, so some APIs use unix signals to inform applications when asynchronous I/O completes.
State management is also more difficult, since state needs to be preserved (not on the stack) to handle the completed task.
This can be done with a continuation, where the state after the aio request is issued is saved and read back from later on.
An event-based approach doesn’t solve all problems – paging and especially page-faulting still causes blocking, and it can be hard to maintain an event-based system, since asynchronous and synchronous APIs must be supported.
Prev: common-concurrency-problems Next: summary-dialogue-on-concurrency