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’.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"
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
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
#!/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";
}
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";
}
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.