Threads – Part I

Because I am an ACM member, earlier today I received a routine email message titled “ACM TechNews, Friday, March 24, 2017”. Most (if not all) articles are typically interesting. This time it was no exception. Sequential Programming Considered Harmful? from IEEE Spectrum by Russ Miller called my attention. If you have not read it yet, please take the time and you will understand my motivation to write this post which will be a part of a short sequence on the subject of threads.

On several occasions I have discussed with different software developers how little threads and their associated issues are understood by software engineers. The article is just one more instance that it is important for software developers to understand threads and learn how to use them.

I have been working with threads for a couple decades. My first interaction was in UNIX with inter-process communications (IPC). When developing complex systems tasks would be initially assigned to a single sequential large program. The idea came up to have a set of processes, each dedicated to a specific task, working together to complete the tasks at hand. Later threads (inside a single process) and fibers showed up.

Currently at work I spend my time working on a storage server for medical images. In actuality it is able to store different type of digital data (i.e., DICOM, text, videos, documents, presentations, etc). The storage server has up to three processes per computer:

Medical images server
Disk storage server
Library storage server

The first process deals with the medical image metadata. The second service deals with managing files on magnetic disk on the host and remote computers and the last process deals creating, writing and reading volumes of different media types in automated libraries. All three servers work in a distributed fashion over the Internet, are fully multi-threaded and each makes use of a database (i.e., MySQL or SQL Server) per instance.

The following is a screen capture of the utility ProcessThreadsView v1.27 by NirSoft of an idle disk manager process:

The utility shows about 30 threads. On a loaded system there could be a couple thousand threads competing for resources and some of them acting on the same object. By the way, most of the storage server processes are implemented in C/C++/C# and Java. Please note that the utility was running on my development machine and not on a production server cluster.

In this series of post I will concentrate on threads implemented using the Java language. Most programming languages have similar or equivalent APIs to create and interact with threads.

For starters, a process has one thread of execution. The main() call on a Java program implements the first and only thread in a sequential program. One of the issues in the disk manager is that servicing requests may take a long time (in computer speak) due to the fact that secondary storage is used to locate and stream back the object. It gets worse if the object is not local and the storage server needs find other servers that have the object and then redirect the request to them. For this reason, the software implements a thread that listens for requests. When a request arrives, some basic checks are performed and the request is placed in a queue. Request processor waits for requests in the queue. It removes them from the queue and after performing request specific validations delegates to a different thread to process the request. That frees the thread to look for the next request. As you can see, depending on utilization, the server could be processing from hundreds to thousands of requests per minute.

There are two items that my description did not cover in detail as illustrated by the following table:

How to start multiple threads.
How to protect the queue of requests.

Like I mentioned, the actual code for the storage server is implemented in mostly implemented in C/C++. For the examples in this sequence of posts I will Java.

There are two basic and somewhat distinct ways to start threads in Java. They are:

Extending the Thread class.
Implementing the Runnable interface.

The initial Java 8 code to determine why we need to use threads:

package canessa.john.threads;

public class Solution {

	// **** cosntants ****
	
	static final int MAX_LOOPS = 7;
	
	/**
	 * Test program implements main thread.
	 */
	public static void main(String[] args) {

		// **** tell user we started the main thread ****
		
		System.out.println("main <<< started ...");
		
		// **** ****
		
		Receiver receiver 	= new Receiver();
		Processor processor = new Processor();
		long beginTime 		= System.currentTimeMillis();	

		// **** loop receiving and processing requests ****
		
		String request 	= "";
		for (int i = 0; i < MAX_LOOPS; i++) {
			
			// **** simulate receiving a single request ****

			request = receiver.nextRequest();
			
			// **** simulate processing a single request ****

			processor.process(request);
		}
		
		// **** tell user we are done with the main thread ****
		
		long endTime = System.currentTimeMillis();
		System.out.println("main <<< duration: " + (endTime - beginTime) + " ms - bye bye !!!");
	}
}

Following is output collected from the console of the Eclipse IDE:

main <<< started ...
nextRequest <<< request ==>REQ_1<==
process <<< done processing request ==>REQ_1<==
nextRequest <<< request ==>REQ_2<==
process <<< done processing request ==>REQ_2<==
nextRequest <<< request ==>REQ_3<==
process <<< done processing request ==>REQ_3<==
nextRequest <<< request ==>REQ_4<==
process <<< done processing request ==>REQ_4<==
nextRequest <<< request ==>REQ_5<==
process <<< done processing request ==>REQ_5<==
nextRequest <<< request ==>REQ_6<==
process <<< done processing request ==>REQ_6<==
nextRequest <<< request ==>REQ_7<==
process <<< done processing request ==>REQ_7<==
main <<< duration: 6813 ms - bye bye !!!

The main program invokes a method that simulates waiting and receiving a client request. The request is just a string. Next the main thread invokes a method to process the request. After the request is processed the program goes back to wait for the next request. For sanity the program loops a fixed number of times.

The code for the two classes responsible for waiting for client requests and processing them follows:

package canessa.john.threads;

import java.util.Random;

public class Receiver {

	// **** constants ****
	
	final long MAX_DELAY = (500 * 1);
	
	// **** members ****
	
	Random rand = null;
	int	id		= 1;
	
	/**
	 * Constructor
	 */
	public Receiver() {
		rand = new Random(MAX_DELAY);
	}
	
	/**
	 * Wait for next request from client.
	 */
	public String nextRequest() {
		String request = "";
		
		// **** delay until next request arrives ****
		
		long delay = Math.abs(rand.nextLong()) % MAX_DELAY;
		try {
			Thread.sleep(delay);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		// **** generate a client request ****

		request = "REQ_" + Integer.toString(this.id++);
		System.out.println("nextRequest <<< request ==>" + request + "<==");

		// **** return new client request ****
		
		return request;
	}
}

package canessa.john.threads;

import java.util.Random;

public class Processor {

	// **** constants ****
	
	final long MAX_DELAY = (500 * 3);
	
	// **** members ****
	
	Random rand = null;
	
	/**
	 * Constructor
	 */
	public Processor() {
		rand = new Random(MAX_DELAY);
	}
	
	/**
	 * Process request
	 */
	public void process(String request) {
		
		// **** delay processing request ****
		
		long delay = Math.abs(rand.nextLong()) % MAX_DELAY;
		try {
			Thread.sleep(delay);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		// **** display we are done processing this request ****
		
		System.out.println("process <<< done processing request ==>" + request + "<==");
	}
}

Note that I randomly picked number 500 to be the maximum delay it would take for the process to receive a client request and that the same number is used to seed the pseudo random number generator. This way the program will behave exactly the same way on each invocation.

The processor of requests also includes a number 1,500 used for a maximum delay. Like I mentioned before, receiving a request takes a lot less time than processing it. I would say that the ratio if more like 1:100,000 but in this example we are using a rate of 1:3.

After all is done, we can see that using the selected constants the code takes about 6.8 seconds to receive and process seven requests and that the program is sequential :o(

If you have comments or questions regarding this or any other post in this blog, please do not hesitate and send me a message. Will reply as soon as possible and will not use your name unless you explicitly allow me to do so.

Regards;

John

john.canessa@gmail.com

Follow me on Twitter:  @john_canessa

Leave a Reply

Your email address will not be published. Required fields are marked *