Suspend and resume a thread using signals
This is a useful question because it illustrates how a seemingly working program using pthreads can be loaded with pitfalls.  I hope that you take this answer in the spirit in which it is intended, which is to help you improve your programming.  I didn't see any major problems with the way you're using signals.

Consider using standard threads

You may already know this, but since C11, threads are now part of the standard.  Using them instead of POSIX threads (pthreads) could make the program, in theory anyway, somewhat more portable.  I'll make no further mention of that, however, and the rest will deal with pthreads, although most of the suggestions still apply.

Think carefully about using non-standard calls

The sigemptyset() call is neither a C nor a POSIX standard.  While it's widely supported, it isn't really portable, strictly speaking.  That may be fine for your purposes, but it's important to know which calls are standard and which are not to improve the portability of your program. (I was actually thinking of sigisemptyset here — my mistake!)

Use a mutex to access shared resources

Both threads are attempting to use printf but that's a problem because that requires access to a shared resource, namely stdout.  Access printf within POSIX threads are guaranteed to be thread safe, but the output could be scrambled (that is, the letters from two different threads could be intermixed).  One could use a mutex to assure that only one thread at a time is using that resource.  Here's one way to do that:

#include &lt;stdarg.h&gt;

static pthread_mutex_t stdout_mutex = PTHREAD_MUTEX_INITIALIZER;

static int locking_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);
    pthread_mutex_lock(&amp;stdout_mutex);
    int retval = vprintf(format, args);
    pthread_mutex_unlock(&amp;stdout_mutex);
    va_end(args);
    return retval;
}


Then replace every instance of printf with locking_printf, including the ones in main since that's a thread, too.

Match return types with function

There's no need to write return NULL; at the end of thread_job because it's a void function.  Also, it's my view that one should omit return 0; from the end of main.  It's always contentious because others disagree (often very passionately!), but here's the rationale.

Use bool types where appropriate

The is_th1_ready and is_th2_ready variables are more appropriately described as bool rather than int variables.  For that reason, I'd recommend using &lt;stdbool.h&gt; and writing their declaration and initialization like this:

static bool is_th1_ready = false;
static bool is_th2_ready = false;


Think about casting

Creating the threads uses this kind of call:

pthread_create(&amp;thread2, NULL, thread_job, (void *)2);


That's fine and appropriate.  However, the later use of that data within the thread is a bit unusual:

if (t_id == (void *)1)
    is_th1_ready = 1;


It's perfectly technically valid, but the more conventional approach is to cast the passed void * into something intelligible to the local thread -- often this is a pointer to a struct containing multiple values.  In this case, I'd probably write it like this instead:

if ((int)t_id == 1)
    is_th1_ready = true;


On my machine this causes a compiler warning because int and void * are not the same size (on this machine, with this compiler), but that's not a problem because we know with certainty that this is, in fact, an int.  Another approach would be to do the cast once in the creation of a local int variable.

Rethink the use of scanf

If the user enters a letter or some other non-numeric input, the scanf input fails and the user loses control of the application.  It's well known that scanf has problems.  For that reason, I'd recommend instead using fgets and then strtod.

char buf[5];  // we don't need a big buffer
fgets(buf, sizeof buf, stdin);
switch(strtol(buf, NULL, 10)) {


This will interpret any "garbage" input as 0, but that's probably OK for this program.  Note also that for POSIX systems, non-numeric input can be detected by looking at the errno value.

Prefer for to while where appropriate

The only use of terminate is within the loop, so I'd suggest changing from while to for like this:

for (bool running = true; running; ) {


Note that I've inverted the sense to eliminate the need to negate.  The compiler would probably have done something like that anyway, but I think it's more clear for human readers this way.

Don't bother killing threads at exit

The return from main or a call to exit() will both cause the threads to be cleaned up and resources returned, so I would probably omit the two pthread_kill calls at the end of main.

Consider more robust error handling

It's nice that the code is checking the return value of sigaction, but should the thread even launch if we can't handle the signals?  It's a design choice, but one worth considering, in my opinion.