What is I/O Bound Process [3 Best Examples] ?

In this tutorial, we will learn about what is I/O bound process using 3 best examples. When we say I/O(input/output) bound process, it really means that the speed of the process is determined by the speed of the I/O or specifically it is limited by the speed of the I/O.

Overview of I/O Bound Process

An I/O (input-output) bound process is a type of computational task that spends a significant amount of its execution time waiting for input/output operations to complete. In other words, the process is often bottlenecked by the speed at which it can read from or write to external resources like disk storage, network connections, user interactions and so on.  Let’s understand this with some day to day use cases.

File Copy – When we copy file from one folder to another in our system, the process involves reading data from the source folder and writing it to the destination folder.  This is an example of input/output bound process as it spends lot of time waiting for data to be read from the source location and writing to the destination location.

Database Queries – This is another example of input/output bound process. When we query a large dataset, database engine needs to fetch data from storage, which might involve disk read operations. The process of reading data from storage and sending it to the application can take time and bottleneck the execution.

 

What is I/O Bound Process [3 Best Examples] ?

Features of I/O bound Process

High I/O Operations – I/O bound processes involve a significant number of input/output operations. These operations include reading from or writing to files., accessing databases, interacting with network resources, or writing for user input.

Low CPU Utilization – Due to the waiting nature of I/O operations, the CPU utilization of an I/O bound process is relatively low. The CPU has to wait for data to become available before it can proceed with processing.

Asynchronous Operations – To mitigate the waiting time, I/O bound processes often use asynchronous programming techniques. This allows the process to initiate I/O operations and continue executing other tasks while waiting for the I/O operations to complete.

Parallelism Potential – In some cases, I/O bound processes can benefit from parallelism by performing multiple I/O operations concurrently. For example, downloading multiple files in parallel or processing multiple database queries simultaneously.

 

What is I/O Bound Process [3 Best Examples] ?

Also read: CPU Bound Processes in Golang [4 Best Examples]

We will implement all the examples in GO. But you can pick any other programming language to implement the concept. In below examples, we will see how I/O bound processes are implemented in different ways in Golang. We will look at concurrent and sequential way of implementing I/O bound processes. Let’s look at each example below.

Example-1: Check URLs Status using Sequential Method

In this example, we will call several websites and check if those websites are active or not. This is one the example of I/O bound process where we have set up a slice of links having six links. We have then set up a for loop where we will  call the function  checkLink(url) to check each of the link.  checkLink(url) function will return the message “<link> is inactive” if website is not reachable else it will print “<link> is active”. We will first look at the sequential implementation of this example and later we will implement the same example using concurrency.

package main

import (

    "fmt"
    "net/http"
    "runtime"
    "time"
)

func main() {

    core := runtime.NumCPU()
    fmt.Println("Total cores available in the system: ", core)

    URLs := []string{

        "https://www.linuxnasa.com/",
        "https://www.youtube.com/",
        "https://www.google.com/",
        "https://go.dev/",
        "https://www.cyberithub.com/",
        "https://www.gigle.com/",
    }

    start := time.Now()
    for _, url := range URLs {
        checkLink(url)
    }

    stop := time.Since(start)
    fmt.Println("Processing took: ", stop)
}

func checkLink(link string) {

    _, err := http.Get(link)
    if err != nil {

        fmt.Println(link, " is inactive")
        return
    }

    fmt.Println(link, " is active")
}
OUTPUT
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\websiteChecker.go
Total cores available in the system: 8
https://www.linuxnasa.com/ is active
https://www.youtube.com/ is active
https://www.google.com/ is active
https://go.dev/ is active
https://www.cyberithub.com/ is active
https://www.gigle.com/ is inactive
Processing took: 2.8315284s

In the above example, There are total 8 cores available and all 8 cores are used to execute the process. The processing time it took to complete the execution is around 2.8s.

 

Example-2: Check URLs Status using Concurrency(Waitgroup) Method

In this example, we will implement the same code as seen in example-1 using concurrency. We will implement it using Waitgroups. Here the logic remains same to check the website. We have introduced Waitgroups from sync package so now the checkLink() function will call the wg.done() function once it completes its execution then only main function execution will happen.

package main

import (

    "fmt"
    "net/http"
    "runtime"
    "sync"
    "time"
)

var wg sync.WaitGroup

func main() {

    cores := runtime.NumCPU()
    fmt.Println("Total cores available in the system:", cores)
    URLs := []string{

        "https://www.linuxnasa.com/",
        "https://www.youtube.com/",
        "https://www.google.com/",
        "https://go.dev/",
        "https://www.cyberithub.com/",
    }

    wg.Add(len(URLs))
    start := time.Now()

    for _, url := range URLs {
        go checkLink(url, &wg)
    }

    wg.Wait()
    stop := time.Since(start)
    fmt.Println("Processing took:", stop)
}

func checkLink(link string, wg *sync.WaitGroup) {

    defer wg.Done()
    _, err := http.Get(link)

    if err != nil {

        fmt.Println(link, "is inactive")
        return
    }

    fmt.Println(link, "is active")
}

OUTPUT

PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\websiteChecker.go
Total cores available in the system: 8
https://www.youtube.com/ is active
https://go.dev/ is active
https://www.google.com/ is active
https://www.cyberithub.com/ is active
https://www.linuxnasa.com/ is active
Processing took: 1.5121061s

In the above code, notice that the processing time it took to complete the execution is around 1.5s which is comparatively low than sequential execution of this code. Hence concurrency helps to do the same job more effectively than the sequential method.

 

Example-3: Check URLs Status using Concurrency(Channel) Method

In this example, we will implement the same code as in example-1 using  concurrency but this time we will use Channels, specifically buffered Channel in Golang.

package main

import (

    "fmt"
    "net/http"
    "runtime"
    "time"
)

func main() {

    cores := runtime.NumCPU()
    fmt.Println("Total cores available in the system:", cores)
    URLs := []string{

        "https://www.linuxnasa.com/",
        "https://www.youtube.com/",
        "https://www.google.com/",
        "https://go.dev/",
        "https://www.cyberithub.com/",
    }

    ch := make(chan string, len(URLs))
    start := time.Now()
    for _, url := range URLs {
        go checkLink(url, ch)
    }

    for len(ch) < len(URLs) {
        // Wait for all values to be sent to the channel
    }

    for range URLs {
        fmt.Println("Channel message: ", <-ch
    }

    close(ch)
    stop := time.Since(start)
    fmt.Println("Processing took:", stop)

}

func checkLink(link string, ch chan string) {

    _, err := http.Get(link)
    if err != nil {
        ch <- fmt.Sprintf("%s is inactive", link)
        return
    }

    ch <- fmt.Sprintf("%s is active", link)
}
OUTPUT
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\websiteChecker.go
Total cores available in the system: 8
Channel message: https://www.youtube.com/ is active
Channel message: https://www.google.com/ is active
Channel message: https://www.cyberithub.com/ is active
Channel message: https://go.dev/ is active
Channel message: https://www.linuxnasa.com/ is active
Processing took: 869.3037ms

In the above code, the processing time taken to complete the process execution is somewhere around 8ms which is fairly low than other two method we saw earlier. Hence concurrency using Channel is the best option  to implement the I/O bound process as the execution time is fast and more number of I/O process can be executed simultaneously.

 

Summary

I/O bound processes are characterized by their heavy reliance on input/output, resulting in significant wait times for these operations to complete. The execution of the process is often bottlenecked by the speed of reading from or writing to external resources, making performance optimization and asynchronous programming important considerations.

Leave a Comment