Threads: Running 2 Functions within a Program at the Same Time

Threads are an aspect of processes. In a sense, they are sub-processes or mini-processes within a process. To have threads, the code for a process must be explicitly written to use them. The programming environment or operating system provide explicit support by supplying functions to create and manage threads within a program. Programmers then include calls to these functions in their code if they want to take advantage of threading.


Below is a pair of programs. They both do the same thing. Namely, print "hello" 5 times and print "world" 5 times. The printing is done by a function. The function call hands the function the raw material to be printed. The function then prints it 5 times, in a loop. Every time the function prints, it pauses for 1-second ( sleep(1) ).

The single-threaded version calls the function once to print "hello," which appears on the screen 5 times; and again thereafter to print "world," which then appears 5 times itself. Overall, with a 1-second pause per printed word, it takes about 10 seconds to run as expected.

The other version takes advantage of threads. It doesn't directly call the print function at all. It calls a function that creates a thread instead ( pthread_create( ) ). It gives this thread-creator 1) the name of the print function, and 2) the word it wants printed. It calls the thread-creator twice, once for "hello" and once for "world." The result is again that "hello" and "world" each appear 5 times on the screen. However, instead of 5 hello's followed by 5 world's, the two words show up on screen interspersed. This demonstrates that 2 copies of the print function are running concurrently, performing printout of the 2 words at the same time. When both have finished their work, the main program continues/terminates. And the program takes only 5 seconds to run, despite containing 10 1-second delays. Obviously those delays must now be coincident or overlapping.

 

Single-threaded version

Multi-threaded version

Duration: 10 seconds

Duration: 5 seconds

Source code:

/* hello_single.c - a single threaded hello world program */

#include <stdio.h>
#define NUM 5

main()
{
  void print_msg(char *);

  print_msg("hello");
  print_msg("world\n");
}

 

 

void print_msg(char *m)
{
  int i;
  for(i=0 ; i<NUM ; i++){
  printf("%s", m);
  fflush(stdout);
  sleep(1);
  }
}

 

Source code:

/* hello_multi.c - a multi-threaded hello world program */

#include <stdio.h>
#include <pthread.h>

#define NUM 5

main()
{
  pthread_t t1, t2; /* two threads */

  void *print_msg(void *);

  pthread_create(&t1, NULL, print_msg, (void *)"hello");
  pthread_create(&t2, NULL, print_msg, (void *)"world\n");
  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
}

void *print_msg(void *m)
{
  char *cp = (char *) m;
  int i;
  for(i=0 ; i<NUM ; i++){
  printf("%s", m);
  fflush(stdout);
  sleep(1);
}
return NULL;
}

 

Screen output:

[root@sputnik threads]# ./hello_single
hellohellohellohellohelloworld
world
world
world
world

Screen output:

[root@sputnik threads]# ./hello_multi
helloworld
helloworld
helloworld
helloworld
helloworld

These examples come from Understanding Linux/Unix Programming by Bruce Molay (Prentice-Hall, 2003). Molay succinctly but aptly communicates the primary features of threads. "A thread is to functions what a process is to programs, an environment in which to run." Our textbook author Stallings presents a thread as a unit of dispatch, a code entity that can be separately and independently scheduled and run by the operating system. Thus just as a process is an instance of a program, a thread is an instance of a function. The idea that 2 or more copies of the same function within a program could be running at the same time is new. But the idea that 2 or more copies of the same program within the computer could be running at the same time is familiar. Extend your understanding of how the operating system schedules and dispatches processes to functions, and you understand threads.

Molay has an interesting analogy. "People do this sort of multithreaded task management all the time. A parent who has to run several errands could do them in sequence or, instead, could take a couple of kids along and have one buy milk at the grocery store, the other return books to the library, and then wait for both to return before going home.

"A thread is like a kid you bring along to run a function for you. If you want to run several errands at once, you bring along several kids. If a program wants to run several functions at once, it creates several threads....Here we are running two instances of the same function at once, only the argument is different. We could have just as easily called two different functions and run those in parallel."

The programmer here uses 2 functions to write the multi-thread version:

   pthread_create( ) - create a new thread
      tell it the new thread name, the function to run within the new thread, and arguments to that function
   pthread_join( ) - wait for termination of a thread
      tell it the thread name, and the calling program will block (not proceed) until the specified thread finishes running

Note that inserting pthread_join( ) for both threads into the main program forces that program not to continue till both threads are finished. This is like the parent not driving home till both kids have completed their assigned errand.

It should be noted that a number of logical problems can arise when you adopt the use of threads. They can perform various kinds of conflicting activities, which are the programmer's responsibility to avoid. These problems are beyond our scope here. However, Molay hints at them by commenting, "Programming with threads is like assigning several people to do several tasks. If you manage the project correctly, you can get work done faster, but you have to make sure that your workers do not get in each other's ways and that they perform tasks in the correct sequence."