Menu

Writing and analysing multi-threaded code on a Raspberry-pi

A C program is created that creates three threads, running for different lengths of time, and executes two shell scripts (One before and one after the multiple-thread creation).

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <time.h>

// **NOTE** fprintf is used to print the messages to terminal and 'standard error
// stream' is used  instead 'standard output', this methods prints the messages 
// in the right sequence instead of buffering them to the end

void* Thread_1(void* arg) // Function for thread 1 
{
struct timeval t0, t1, time_diff;  // Used for testing the 
gettimeofday(&t0,NULL);             // actual time taken by the thread 
fprintf(stderr,"Entered the first thread\n");   
sleep(1);   // sleep for 1 second
gettimeofday(&t1,NULL);   //  time after 
timersub(&t1,&t0,&time_diff); // store the difference in before and after time
fprintf(stderr,"\nLeft the first thread\n"); 
fprintf(stderr,"doSomething (thread %ld) took %d.%06d sec\n",(long)pthread_self()
    ,time_diff.tv_sec,time_diff.tv_usec); // print time taken with the accuracy
                        // of microseconds 
pthread_exit(0); // exit the thread
}

void* Thread_2(void* arg) // Function for thread 2 
{
fprintf(stderr,"Entered the second thread\n"); 
sleep(7); // sleep for seven seconds
fprintf(stderr,"\nLeft the second thread\n");
pthread_exit(0); // exit the thread
}

void* Thread_3(void* arg) // Function for thread 3
{
fprintf(stderr,"Entered the third thread\n");
sleep(5); // sleep for five seconds
fprintf(stderr,"\nLeft the third thread\n");
pthread_exit(0);// exit thread
}


int main()
{
pid_t pid; // declare a variable 'pid' of type pid_t
pid =fork();// fork a child and parent process and assign the return value to 'pid' 

if(pid==0) // if inside child process (fork's return value zero means in child process)
{
char * path1="./first.sh"; // path for the first shell script
char * arg1[]={"./first",NULL}; // default argument needed by 'execvp' function
execvp(path1,arg1); // executes the 'first.sh' shell script
}

else if(pid > 0) // If Inside the parent process
{
//Thread ID
pthread_t tid1; // declare a variable 'tid1' of type pthread_t
pthread_t tid2; // declare a variable 'tid2' of type pthread_t
pthread_t tid3; // declare a variable 'tid3' of type pthread_t

//Create the threads
pthread_create(&tid1,NULL,&Thread_1,NULL);//initalize tid1 and run Thread_1 function 
pthread_create(&tid2,NULL,&Thread_2,NULL);//initalize tid1 and run Thread_1 function 
pthread_create(&tid3,NULL,&Thread_3,NULL);//initalize tid1 and run Thread_1 function 

//wait until the thread has done its work
pthread_join(tid1,NULL); // wait for thread with thread ID tid1, no return value needed
pthread_join(tid2,NULL); // wait for thread with thread ID tid1, no return value needed
pthread_join(tid3,NULL); // wait for thread with thread ID tid1, no return value needed
//sleep(10);
wait(NULL); // Wait for the child process to finish (If it hasn't)
}
else if(pid < 0) // If there was an error in forking
{
perror("Fork failed"); //Print an error messsage
exit(1); // exit with return value 1 
}

pid=fork(); // Create another child process and parent process  
if(pid == 0) // If in child process 
{
char *path2="./second.sh"; // path to second shell script
char *arg2[]={"./second",NULL}; // default argument needed for execvp
execvp(path2,arg2);// execute the 'second.sh' shell script 
}
else if(pid>0) 
{
wait(NULL); // wait for the child process to finish
printf("\nExiting the main loop\n"); 
}
}
//to compile we use  = gcc sum_on_t.c  -pthread -o some_name 
// we use the c99 format because we declared the variable in the for loop which can't be done in the linux C version

It is to be noted that ‘fprintf’ function is used to identify the printing stream to be ‘stderr (standard-error)’ instead of ‘printf’ which uses ‘stdout (standard-out) as default because in the multithreaded code, the printf function seems to be stacking up the message to be printed at the end program instead of the right sequence.

The three functions Thread_1, Thread_2, Thread_3 essentially do the same thing, sleeping for 1-sec, 7-sec and 5-sec respectively.The return type of the functions is a void-pointer as the thread creation function accepts a pointer-to-NULL as the argument for Function-call. ‘gettimeofday’ function is used in the first thread to see the time spent in the thread with more accuracy (i.e. micro-secs).

‘Fork’ function is used to spawn the child processes that execute the two shell-scripts. The threads are created in the parent process. ‘pid’ variable of type pid_t is used to store the return type of ‘Fork()’ to check if the program-counter is inside the child process or parent process (the process-id of child process is returned when inside parent). Using Fork() is important when using exec() function to start another process, because exec() exits the calling process before executing the specified file. Therefore the child process’s will become the respective shell scripts when the exec() function is reached. If Fork()’s return value is less than zero, means the child process wasn’t created and an error message is printed, exiting with the a return-value 1.

Variables of type ‘pthread_t’ are declared to reserve the memory space for thread-IDs which can be pointed to in the thread-creation function. ‘pthread_create()’ function creates the respective threads taking the thread-ID and the Thread-function as the first and third arguments respectively. ‘pthread_join()’ function waits for the specified thread to finish executing before exiting the parent process. The program is compiled using the ‘GCC’ compiler.

Top Command

Two shell scripts are used to run the ‘top’ command at different times to look at the system information when multithreaded code is run. The figure below shows the Top command running with ‘H’ attribute (Showing Threads) when the multithreaded compiled-C-program is running. The multiple-CPU (multi-core) breakdown can be viewed by pressing 1. This shows that the Kernel makes use of multiple cores when the Kernel-level-threads created by pthread_create are running. This configuration of top can be saved by pressing ‘shift+w’.
Top command running in a Rasbian (Linux) Terminal

Shell Scripts

echo  "\nEntered the first shell script\n" #Announce that shell script is executed
top -H -b -n 1 |grep Threads > Threads.txt #run the top command in |thread mode '-H'|
# with |batch-mode text '-b'| and |run it once '-n 1'|
# Grab the line with 'Thread' word in the string 
#the Output is piped to Threads.txt file 

top -H -b -n 1 -w 14 |grep top >>Threads.txt  #run the top command in |thread mode '-H'|
# with |batch-mode text '-b'| and |run it once '-n 1'| with a width of 14 '-w 14'
#Grab the line with 'top' word in the line of string   
#the Output is piped to Threads.txt file 

echo  "This is the data put into the first text file\n"
 
cat Threads.txt # Output the contents of the text file

echo "\n"

C_prog_pid="$(pidof Thread_runner)" # Get the process Id of the executable Thread_runner

echo  "$C_prog_pid is the process ID of the running executable\n"

First Shell Script
The codeblock above shows the shell script executed by the C program when the multiple threads are running. It runs one iteration of ‘top’ command twice to grab and store the ‘Thread-row’ and the ‘top-row’ in a text file called ‘Threads.txt’ The contents of the text-file are then printed on the terminal. The script also gets the process-ID of the C-program-executable using ‘pidof’ command and prints it on the terminal.

echo "\nEntered the second shell script\n"

    echo "This is the data put into the second text file\n"

    top -H -b -n 1 |grep Threads > Threads_after.txt #run the top command in |thread mode '-H'|
    # with |batch-mode text '-b'| and |run it once '-n 1'|
    # Grab the line with 'Thread' word in the string 
    #the Output is piped to 'Threads_after.txt' file 
    
    top -H -b -n 1  -w 14 |grep top >> Threads_after.txt  #run the top command in |thread mode '-H'|
    # with |batch-mode text '-b'| and |run it once '-n 1'| with a width of 14 '-w 14'
    #Grab the line with 'top' word in the line of string   
    #the Output is piped to Threads.txt file 
     
    cat Threads_after.txt  # Output the contents of the text file
    
    perl search_th.pl # Run the perl script to Output the data in a summarized fashion
    

Second Shell Script
The second shell script does essentially the same thing as the first shell script (after the threads have finished executing) but it also executes the ‘Perl-script’ that Outputs the data stored in the text files in a more readable, user-friendly fashion.

Perl Script

This section looks at the Perl-script used to read the data stored in the text files and print the data in the terminal, explaining to the non-technical user the analysis of the multi-threaded code

#!/usr/bin/perl

use strict;  
use warnings;

print "\n\n THE PERL SCRIPT BEGINS \n\n"; #Annouce the entry into the perl script

my $Threads_total_before; # scalar variable for total threads in first text file
my $Threads_total_after; # scalar variable for total threads in the second text file
my $Threads_running; # scalar variable for running threads
my $Threads_sleeping; # scalar variable for running threads
my $start_minute; # scalar variable for start time's minute part
my $start_second; # scalar variable for start time's second part
my $end_minute; # scalar variable for end time's minute part
my $end_second; # scalar variable for end time's second part 
my @my_array; #array variable for storing the words in a row of a file
my @start_time; #array variable for storig the hours,minutes and secs of start time
my @end_time; #array variable for storing the hours,minutes and secs of end time 
my $row; #scalar variable for storing the row of a file

my $filename = 'Threads.txt'; # name of the text file to be read
open(my $fh,'<:encoding(UTF-8)',$filename) #Open 'Threads.txt' as a big string '$fh'
        or die "Could not open file '$filename' $!"; # exit with a message if file can't open

while( $row = <$fh>) #run a while loop with range equal to the number of rows in $fh string 
{
chomp $row; # Get rid of any empty newlines
@my_array=split(' ',$row); #split the words in the row based on 'spaces' and store them in @my_array  
if($row =~ /Threads/) # using regex '=~ //' determine if the word 'Thread' appears in the row
{
$Threads_total_before=$my_array[1]; #get the second element of @my_array which is total threads
$Threads_running=$my_array[3]; #get the fourth element of @my_array which is running threads
$Threads_sleeping=$my_array[5];# get the sixth element of @my_array which is sleeping threads
}
elsif($row =~ /top/) # using regex '=~ //' check if the word 'top' appears in the row
{
 @start_time=split(':',$my_array[2]); #split digits in the array based on ':' & store in @start_time  
 $start_minute=$start_time[1]; # store the 2nd element of @start_time, which is minutes
 $start_second=$start_time[2]; # store the 2nd element of @start_time, which is minutes
}

}

#      PRINT THE READINGS IN UNDERSTABDABLE TEXT
print "Total number of threads are $Threads_total_before\n";
print "$Threads_running of the $Threads_total_before are running\n";
print "$Threads_sleeping threads are taking a nap\n";
print "Start minute : $start_minute\n";
print "Start second : $start_second\n";

my $filename2 = 'Threads_after.txt'; # name of the second file to be read
open(my $fh2,'<:encoding(UTF-8)',$filename2)  #Open 'Threads_after.txt' as a big string '$fh2' 
        or die "Could not open file '$filename' $!"; # exit with a message if file can't open

while( $row = <$fh2>) # run a while loop with range equal to the number of rows in $fh2 string
{
chomp $row; # Get rid of empty newlines
@my_array=split(' ',$row); #split the words in the row based on 'spaces' and store them in @my_array
if($row =~ /Threads/)  # using regex '=~ //' check if the word 'Thread' appears in the row
{
$Threads_total_after=$my_array[1];#get the second element of @my_array which is total threads 
$Threads_running=$my_array[3];#get the fourth element of @my_array which is runnning threads
$Threads_sleeping=$my_array[5];#get the sixth element of @my_array which is sleeping threads
}
elsif($row =~ /top/)  # using regex '=~ //' check if the word 'Top' appears in the row
{
 @end_time=split(':',$my_array[2]); #split the words in the @my_array[2] based on ':'  

 $end_minute=$end_time[1]; # get the second element of end-time, which is minutes
 $end_second=$end_time[2]; # get the third element of end-time, which is seconds
}

}

#               PRINT THE DATA IN UNDERSTANDABLE TEXT
print "\nTotal number of threads are $Threads_total_after\n";
print "$Threads_running of the $Threads_total_after are running\n";
print "$Threads_sleeping threads are taking a nap\n";
print "End minute : $end_minute\n";
print "End second : $end_second\n";

# Get the number of threads spawned by comparing the total threads from both the text file
my $number_of_threads = $Threads_total_before - $Threads_total_after;
print "\n$number_of_threads threads were created by the executable\n";

#Get the time taken for all threads to finish executing by comparing time from both text files
if($end_minute == $start_minute) # If less than a minute was spent
{
 my $diff = $end_second - $start_second; # get the time difference
 print "\n$diff seconds were taken to complete all ",  
           "the threads spawned by the executable\n";
}
else # if more than a minute was taken
{
 my $diff = (($end_minute - $start_minute) * 60 ) + 
		($end_second - $start_second); # Get the time difference
 print "\n$diff seconds were taken to complete all ",
           "the threads spawned by the executable\n";	
}

print " \n\n PERL SCRIPT ENDS \n\n" # Announce the exit from perl script

Perl Script for parsing the stored data
The line #!/usr/bin/perl tells the system where the compiler resides, when the script is run using ‘Perl’ command. The ‘strict’ pragma sets certain rules for the programmer that helps writing the correct format of code. For example, unlike most programming languages, Perl doesn’t have a main-loop. The ‘strict’ pragma therefore forces the programmer to declare all variable in a lexical scope. So, a variable declared in a block-of-code (e.g. While-loop) cannot be used outside that block-of-code. The ‘warning’ pragma helps that compiler in the detection of errors and potential problems.
The variables declared at the top of file are declared outside any block of code so they could be used as global-variables in any block of code.

The open function opens ‘Threads.txt’ file with UTF-8 encoding (UTF-8’s wide range of language support makes it ideal). Getting rid of any empty newline, using chomp() function in the while loop, the loop reads through the 2 rows in ‘Threads.txt’. The words in each row are split based on ‘space’ and stored in an array-variable. In the first row, the second, fourth and sixth element are used to store the total, running and sleeping threads respectively. In the second row, second element of the array, which is the start time is split based on the symbol ‘:’ and stored in another array. The second third element of the ‘start_time’ array are used to store the minute part and second part of the start time.

Next, similar steps are followed for opening and reading the second text file. The second text file holds identical information with different values for that variables of interest. The reading and data separation process for the second text file is therefore same as the first text file.
 #Get the time taken for all threads to finish executing by comparing time from both text files
if($end_minute == $start_minute) # If less than a minute was spent
{
 my $diff = $end_second - $start_second; # get the time difference
 print "\n$diff seconds were taken to complete all ",  
           "the threads spawned by the executable\n";
}
If less than a minute was spent

else # if more than a minute was taken
{
my $diff = (($end_minute - $start_minute) * 60 ) + 
        ($end_second - $start_second); # Get the time difference
print "\n$diff seconds were taken to complete all ",
        "the threads spawned by the executable\n";	
}
If more than a minute was spent
The sections above compares the data from the two text files. It checks the Total number of threads while the Multithreaded program is running and after it has finished Executing. It also looks at the difference between the Multithreaded-Program start time and end time. To see the total time taken by the whole process

Running the executable

Finally to run the multithreaded-code and get back the background analysis, the compiled executable of ‘Thread_runner.c’ which is ‘Thread_runner’ is executed in the Terminal by typing: “ ./Thread_runner “ This will give the following output: It can be seen that the total time taken to run the multi-threaded program is 7 seconds. The three threads in the program run for 1 second, 7 second and 5 second respectively, which totals to 13 seconds. In a single-threaded program, the program would take 13 seconds to run the three functions sequentially, however in this case, the program makes use of the four cores in the Raspberry-pi processor to run each thread on separate cores. Hence only 7 seconds, which is total time taken by the second thread (i.e. The longest running thread), are used to reach the end of the executable.

"I consider that the golden rule requires that if I like a program I must share it with other people who like it."

Richard Stallman

Importance of Multithreading

We have now reached the stage when CPU clock speeds are no longer increasing, or, have slowed down drastically compared to the exponential rise in the last five decades.

More’s Law, that states that the number of transistors in a dense integrated circuit, doubles every year, is no longer applicable to the current trend. This is due to various factors including the reached limit of ‘Dennard scaling’ (that states that the power needing to run transistors in a unit volume stays constant.) meaning that with increasing transistors more power would be needed. The shrinking size of the transistors also makes their accuracy more susceptible to thermal losses which would also be increasing with increasing power supply.

Even with more efficient cooling methods available in the future, the reduction in transistor size has also nearly reached its limit. The current size of 10-20 nano-meters can be further reduced to 5-7 nano-meters but beyond that quantum effects would prevent the transistor from working properly. It is clear that increase in CPU clock speed is not the way forward but rather it is the increase in the number of CPU that would compensate for the need of more computing power needed for the more sophisticated future applications.

Already, processor companies have designed and manufactured CPU’s with over hundred cores but it is the software industry that is now lagging behind. To utilize multiple cores in a system, the software must be written in a multi-threaded fashion that makes use of the available cores. Writing multithreaded code, however, has proven very difficult with the need to keep track of multiple threads that share the same memory have access and can make changes to the same variable. Though this makes the context-switching a lot less expensive between the threads, it also makes the program a lot less predictable and debugging such programs can get very hard.

All in all, despite the lack of elegant multi-threading software solutions at the moment, Multi-threading is nevertheless the future of software programming and extra emphasis must be given to it in learning stages of programmers.