Asynchronous I/O

From Wikipedia, the free encyclopedia

Asynchronous I/O, or non-blocking I/O, is a form of input/output processing that permits other processing to continue before the transmission has finished.

Input and Output operations on a computer can be extremely slow compared to the processing of data. An I/O device can incorporate mechanical devices which must physically move, such as a hard drive seeking a track to read or write; extremely slow compared to merely moving electrons. For example, during a disk operation that takes ten milliseconds to perform, a processor that is clocked at one gigahertz could have performed ten million instruction-processing cycles.

A simple approach to I/O would be to start the access and then wait for it to complete. But such an approach (called synchronous I/O or blocking I/O) would block the progress of a program while the communications is in progress, leaving system resources idle. When a program makes many I/O operations, this means that the processor can spend almost all of its time idle waiting for I/O operations to complete.

Alternatively, it is possible, but more complicated, to start the communication and then perform processing that does not require that the I/O has completed. This approach is called asynchronous input/output. Any task that actually depends on the I/O having completed (this includes both using the input values and critical operation that claim to assure that a write operation has been completed) still needs to wait for the I/O operation to complete, and thus is still blocked, but other processing which does not have a dependency on the I/O operation can continue.

Contents

[edit] Application

A great many operating system functions exist to implement asynchronous I/O at many levels. In fact, one of the main functions of all but the most rudimentary of operating systems is to perform at least some form of basic asynchronous I/O, though this may not be particularly apparent to the operator or programmer. In the simplest software solution, the hardware device status is polled at intervals to detect whether the device is ready for its next operation. DMA can greatly increase the efficiency of a polling-based system, and hardware interrupts can eliminate the need for polling entirely. Multitasking operating systems can exploit the functionality provided by hardware interrupts, whilst hiding the complexity of interrupt handling from the user. Spooling was one of the first forms of multitasking designed to exploit asynchronous I/O. Finally, multithreading and explicit asychronous I/O APIs within user processes can exploit asynchronous I/O further, at the cost of extra software complexity.

Asynchronous I/O is used to promote throughput, latency, and/or responsiveness.

[edit] Forms

All forms of asynchronous I/O open applications up to potential resource conflicts and associated failure, careful programming (often using mutex, semaphores, etc.) is required to prevent this.

When exposing asynchronous I/O to applications there are a few broad classes of implementation. The form of the API provided to the application does not necessarily correspond with the mechanism actually provided by the operating system, emulations are possible. Furthermore, more than one method may be used by a single application, depending on its needs and the desires of its programmer(s). Many operating systems provide more than one of these mechanisms, it is possible that some may provide all of them. The major forms are:

[edit] Process

Available in early Unix. With a multitasking operating system each I/O flow can be allocated to a separate (sub)process. This is a heavyweight solution, and coordinating the flows can be difficult. Because of the natural process isolation it may not even be possible to get the desired behavior; the attempt to partition the task for asynchronous I/O may do more harm than good.

[edit] Polling

Variations:

  • Error if it cannot be done yet (reissue later)
  • Report when it can be done w/o blocking (then issue it)

Available in traditional Unix. Its major problem is that it can waste CPU time polling repeatedly when there is nothing else for the issuing process to do, reducing the time available for other processes. Reducing this waste by sleeping a bit before the next poll results in increased latency of I/O operations, reducing throughput and responsiveness. Also, if there is more than one polling place in a process it is highly likely that not all potential I/O will be considered at each point (else why even have multiple polling places), at best affecting only latency and/or throughput and at worst inducing difficult to reproduce failures. It can also be difficult or impossible to even find all blocking code points in the process to refit them with polling versions, preventing the process from fully satisfying whatever criteria prompted the move toward asynchronous I/O in the first place.

[edit] Select loop

A variation on the theme of polling, a select loop uses the select system call to sleep until a condition occurs on a file descriptor (e.g. data available for reading), a timeout occurs, or a signal is received (e.g. when a child process dies). By examining the return parameters of the select call, the loop finds out which file descriptor has changed and executes the appropriate code. Often, for ease of use, the select loop will be implemented as an event loop, perhaps using callback functions. While this method is highly efficient and reliable, it depends heavily on the Unix paradigm that "everything is a file"; any blocking I/O that does not involve a file descriptor will block the process. The select loop also relies on being able to involve all I/O in the central select call; libraries that conduct their own I/O are particularly problematic in this respect.

[edit] Signals (interrupts)

Available in BSD and POSIX Unix. I/O is issued asynchronously, and when it is complete a signal (interrupt) is generated. As in low-level kernel programming, the facilities available for safe use within the signal handler are limited, and the main flow of the process could have been interrupted at nearly any point, resulting in inconsistent data structures as seen by the signal handler. The signal handler is usually not able to issue further asynchronous I/O by itself.

The Signal approach, though relatively simple to implement within the OS, brings to the application program the unwelcome baggage associated with writing an operating system's kernel interrupt system.

[edit] Callback functions

Available in Mac OS and VMS. Bears many of the characteristics of the Signal method as it is fundamentally the same thing, though rarely recognized as such. The difference is that each I/O request usually can have its own completion function, whereas the Signal system has a single callback.

A potential problem is that stack depth can grow unmanageably, as an extremely common thing to do when one I/O is finished is to schedule another. If this should be satisfied immediately, the first callback is not 'unwound' off the stack before the next one is invoked. Systems to prevent this (like 'mid-ground' scheduling of new work) add complexity. The separation of textual (code) and time (event) flows provides fertile ground for errors.

[edit] LWP/threads

Available in more modern Unixes. Like the Process method, but without the data isolation that hampers coordination of the flows. This lack of isolation introduces its own problems, usually requiring kernel-provided synchronization mechanisms and thread-safe libraries. Each LWP/thread itself uses traditional blocking synchronous I/O. The separation of textual (code) and time (event) flows provides fertile ground for errors.

[edit] Completion queues

Available in Solaris and DNIX. I/O requests are issued asynchronously, but notifications of completion are provided via a synchronizing queue mechanism in the order they are completed. Usually associated with a state-machine structuring of the main process (event-driven programming), which can bear little resemblance to a process that does not use asynchronous I/O or that uses one of the other forms, hampering code reuse. Does not require additional special synchronization mechanisms or thread-safe libraries, nor are the textual (code) and time (event) flows separated.

[edit] Event Flags

Available in VMS. Bears many of the characteristics of the Completion queue method as it is essentially a completion queue of depth one. To simulate the effect of queue 'depth', an additional event flag is required for each potential unprocessed (but completed) event, or event information can be lost. Waiting for the next available event in such a clump requires synchronizing mechanisms that may not scale well to larger numbers of potentially parallel events.

[edit] External links