This is also an example program for:

  • How to hide input for entering password (Console ECHO OFF/ON),
  • How to get input from keyboard without pressing ENTER,
  • How to restrict keyboard to get only one character as input at a time,
  • How to suspend/resume multiple threads effectively, etc..

After an exhaustive googling to find a perfect (clearly understandable) way of doing suspend/resume threads and having went through many not much satisfying sort of producer/consumer examples, I wrote this program.

There are many ways of doing thread suspend/resume:

  • Using Mutexes and Boolean Variables,
  • Using Thread Conditions,
  • Using Semaphores,
  • Using Signals,
  • Using Message Passing Interface, etc.

This program employs a combination of Boolean Variables, Mutexes and Thread Conditions.

The program is simple. The main thread (function main()) creates/initializes and launches four threads which run simultaneously. They are:

  1. watch_for_user_keypress_thread, which goes into a loop and wait for user input and suspend/resume other three threads according to user keypress. This thread also handles terminal settings (i.e. tty console settings).
  2. func_one_thread, which is a resource independent thread that counts variable count_one and prints it in STDERR stream.
  3. func_two_thread, which is also resource independent and does the same process like func_one, but on counter count_two.
  4. func_three_thread, adds count_one and count_two and prints the result along with its own counter count in STDERR stream.

Inputs to the program are:

  • 1 – is a toggle switch to suspend/resume thread_one
  • 2 – is a toggle switch to suspend/resume thread_two
  • 3 – is a toggle switch for thread_three
  • 0 – ends watch_for_user_keypress and signals all the threads to stop their execution and quit
  • Other keypress – nothing happens

The program is not complicated and is self-explanatory.

// multitask_multithread.c

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

#define TRUE 	1
#define FALSE 	0

// Current tty console settings variable
static struct termios tty_state_current;

// Threads 1, 2 and 3 declared as global
pthread_t func_one_thread, func_two_thread, func_three_thread;

// Thread conditions
pthread_cond_t thread_cond_one, thread_cond_two, thread_cond_three;

// Thread mutex to go with thread conditions
pthread_mutex_t mutex_flag;

// Boolean flags
short tflag_one_on, tflag_two_on, tflag_three_on, quit_flag = FALSE;

// Counters for threads 1 and 2
int count_one, count_two = 0;

// tty console ECHO ON
int echo_on() {
  struct termios tty_state;

  if(tcgetattr(0, &tty_state) < 0)
    return -1;
  tty_state.c_lflag |= ECHO;
  return tcsetattr(0, TCSANOW, &tty_state);
}

// tty console ECHO OFF
int echo_off() {
  struct termios tty_state;

  if(tcgetattr(0, &tty_state) < 0)
    return -1;
  tty_state.c_lflag &= ~ECHO;
  return tcsetattr(0, TCSANOW, &tty_state);
}

// Restore tty console settings to its previous original state
void restore_terminal_settings(void) {
  tcsetattr(0, TCSANOW, &tty_state_current);
}

// Disable waiting for ENTER and accept only one keypress as input
void disable_waiting_for_enter(void) {
  struct termios tty_state;

  tcgetattr(0, &tty_state_current);  // Get current tty console settings
  tty_state = tty_state_current;
  tty_state.c_lflag &= ~(ICANON | ECHO);
  tcsetattr(0, TCSANOW, &tty_state);
  atexit(restore_terminal_settings); // Very handy function to restore settings
}

// Take user keypress and suspend/resume other threads accordingly
void *watch_for_user_keypress() {
  char getKeyPress;

  disable_waiting_for_enter();
  echo_off();

  do {
    getKeyPress = getchar();
    switch (getKeyPress) {
      case 49:
        if(!tflag_one_on) {
          tflag_one_on = TRUE;
          pthread_mutex_lock(&mutex_flag);
          pthread_cond_signal(&thread_cond_one);
          pthread_mutex_unlock(&mutex_flag);
        } else {
          tflag_one_on = FALSE;
        }
        break;
      case 50:
        if(!tflag_two_on) {
          tflag_two_on = TRUE;
          pthread_mutex_lock(&mutex_flag);
          pthread_cond_signal(&thread_cond_two);
          pthread_mutex_unlock(&mutex_flag);
        } else {
          tflag_two_on = FALSE;
        }
        break;
      case 51:
        if(!tflag_three_on) {
          tflag_three_on = TRUE;
          pthread_mutex_lock(&mutex_flag);
          pthread_cond_signal(&thread_cond_three);
          pthread_mutex_unlock(&mutex_flag);
        } else {
          tflag_three_on = FALSE;
        }
    }
  } while(getKeyPress != 48);

  quit_flag = TRUE;

  pthread_cond_broadcast(&thread_cond_one);
  pthread_cond_broadcast(&thread_cond_two);
  pthread_cond_broadcast(&thread_cond_three);

  //echo_on(); // No need for this since atexit() restores previous settings
  pthread_exit(NULL);
}

void *func_one() {
  pthread_mutex_lock(&mutex_flag);
  pthread_cond_wait(&thread_cond_one, &mutex_flag);
  pthread_mutex_unlock(&mutex_flag);

  if(quit_flag) pthread_exit(NULL);

  while(TRUE) {
    if(!tflag_one_on) {
      pthread_mutex_lock(&mutex_flag);
      pthread_cond_wait(&thread_cond_one, &mutex_flag);
      pthread_mutex_unlock(&mutex_flag);
    }
    if(quit_flag) break;
    fprintf(stderr, "<<--%d-->> ", ++count_one);
    sleep(1);
  }

  pthread_exit(NULL);
}

void *func_two() {
  pthread_mutex_lock(&mutex_flag);
  pthread_cond_wait(&thread_cond_two, &mutex_flag);
  pthread_mutex_unlock(&mutex_flag);

  if(quit_flag) pthread_exit(NULL);

  while(TRUE) {
    if(!tflag_two_on) {
      pthread_mutex_lock(&mutex_flag);
      pthread_cond_wait(&thread_cond_two, &mutex_flag);
      pthread_mutex_unlock(&mutex_flag);
    }
    if(quit_flag) break;
    fprintf(stderr, "<<oo%doo>> ", ++count_two);
    sleep(1);
  }

  pthread_exit(NULL);
}

void *func_three() {
  int count = 0;

  pthread_mutex_lock(&mutex_flag);
  pthread_cond_wait(&thread_cond_three, &mutex_flag);
  pthread_mutex_unlock(&mutex_flag);

  if(quit_flag) pthread_exit(NULL);

  while(TRUE) {
    if(!tflag_three_on) {
      pthread_mutex_lock(&mutex_flag);
      pthread_cond_wait(&thread_cond_three, &mutex_flag);
      pthread_mutex_unlock(&mutex_flag);
    }
    if(quit_flag) break;
    fprintf(stderr, "<<==%d::%d==>> ", ++count, count_one+count_two);
    sleep(1);
  }

  pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
  pthread_t watch_for_user_keypress_thread;

  // Thread mutex initialization
  pthread_mutex_init(&mutex_flag, NULL);

  // Thread condtions initialization
  pthread_cond_init(&thread_cond_one, NULL);
  pthread_cond_init(&thread_cond_two, NULL);
  pthread_cond_init(&thread_cond_three, NULL);

  // Thread creation
  pthread_create(&watch_for_user_keypress_thread, NULL, watch_for_user_keypress, NULL);
  pthread_create(&func_one_thread, NULL, func_one, NULL);
  pthread_create(&func_two_thread, NULL, func_two, NULL);
  pthread_create(&func_three_thread, NULL, func_three, NULL);

  // main() launches these four threads and starts waiting for their completion
  pthread_join(watch_for_user_keypress_thread, NULL);
  pthread_join(func_one_thread, NULL);
  pthread_join(func_two_thread, NULL);
  pthread_join(func_three_thread, NULL);

  // All threads exited normally
  printf("\n");

  return 0;
}

Compile it.

$ gcc multitask_multithread.c -o multitask_multithread -lpthread

The output looks like:

$ ./multitask_multithread
<<–1–>> <<–2–>> <<–3–>> <<–4–>> <<–5–>> <<–6–>> <<oo1oo>> <<–7–>> <<oo2oo>> <<–8–>> <<oo3oo>> <<–9–>> <<oo4oo>> <<–10–>> <<oo5oo>> <<–11–>> <<oo6oo>> <<–12–>> <<–13–>> <<–14–>> <<–15–>> <<–16–>> <<–17–>> <<–18–>> <<==1::24==>> <<==2::24==>> <<==3::24==>> <<oo7oo>> <<==4::25==>> <<oo8oo>> <<==5::26==>> <<oo9oo>> <<==6::27==>> <<–19–>> <<oo10oo>> <<==7::29==>> <<–20–>> <<oo11oo>> <<==8::31==>> <<–21–>> <<oo12oo>> <<–22–>> <<oo13oo>> <<–23–>> <<oo14oo>> <<–24–>> <<–25–>> <<–26–>> <<–27–>> <<–28–>> <<–29–>> <<–30–>> <<oo15oo>> <<–31–>> <<oo16oo>> <<–32–>> <<oo17oo>> <<–33–>> <<==9::50==>> <<oo18oo>> <<–34–>> <<==10::52==>> <<oo19oo>> <<–35–>> <<==11::54==>> <<oo20oo>> <<–36–>> <<==12::56==>> <<oo21oo>> <<–37–>> <<==13::58==>> <<oo22oo>>

Note 1: Bad programming ethics. Any outputs of a program are not supposed to be printed onto error (STDERR) stream. Only errors should be printed using STDERR. This sort of bad ethics affects the usage of standard pipes>”, “<” and “|” and other operations.

Note 2: Instead of printing normal output onto STDERR, we can use functions like kbhit()/getch() or other workarounds that suppress “keyboard input wait” hindering background threads printing onto STDOUT stream.

Thanks for the read and please leave comments 🙂