Home > Back-end >  pthread_cond_wait() only wakes when value set to 0
pthread_cond_wait() only wakes when value set to 0

Time:12-08

I am somewhat new to threads but have been reading up on pthreads and trying to write a test program that will eventually be used in an application. The basic operation for this test app is to:

  • spawn two threads,
  • allow a user to enter a number (sender thread) that is stored in a value of a conditioned variable and then use a signal to wake up a second thread (receiver thread).

I am showing the code and the output screen to show the results of the test output. Basically, when I enter a number other than 0 the signal does not wake up the second thread. When a 0 is entered the second thread wakes up and performs as expected (i.e. exits). The question is why does the second thread not wake up for other numbers (i.e. 1 or 2). I have tried this with both a pthread_cond_timedwait() and pthread_cond_wait() but I get the same results in either case. I would appreciate any and all suggestions. Thanks.

//============================ terminal screen =================================
enter a message to send:
0 - exit
1 - message 1
2 - message 2
1
messageSender: msg.value: 1
messageSender: signal sent!
2
messageSender: msg.value: 2
messageSender: signal sent!
0
messageSender: msg.value: 0
messageSender: signal sent!
exit pthread_cond_timedwait, testVal: 1 value: 0
0
messageReceiverThread msg.value received: 0
exiting
//================= apps/pthread_com_example.h =================================

#ifndef PTHREAD_COM_EXAMPLE_H_INCLUDED
#define PTHREAD_COM_EXAMPLE_H_INCLUDED


//  | added for thread com example RLB 04Dec2021
//  V
#if(defined PTHREAD_COM_EXAMPLE)
#include <stdio.h>       // standard I/O routines
#include <stdlib.h>      // rand() and srand() functions
// number of threads used to service requests
#define NUM_HANDLER_THREADS 2

/* format of a message structure */
typedef struct  {
    pthread_mutex_t mutex;  // message mutex
    pthread_cond_t  cond;   // message condition variable
    int testVal;            // use this as the predicate
    int value;              // value to be passed
}message_t;
//  ^
//  | added for thread com example RLB 04Dec2021
#endif


#endif // PTHREAD_COM_EXAMPLE_H_INCLUDED

//=================== pthread_com_example app ==============================
#include <iostream>
#include <pthread.h>
#define PTHREAD_COM_EXAMPLE
#include <apps/pthread_com_example.h>
#include <unistd.h>
#include <sys/time.h>     // struct timeval definition

#if(defined PTHREAD_COM_EXAMPLE)
/* global mutex for example. assignment initializes it.
   note: a RECURSIVE mutex is used, since a handler
   thread might try to lock it twice consecutively. */

#define MSG1  1    // trigger messasge 1
#define MSG2  2    // trigger message 2
#define MSGX  0    // message to exit

std::string message1 = "this is an example message in response to A";
std::string message2 = "this is an example message in response to B";

struct timeval now;
struct timespec timeout;
message_t msg = {
    // initialize the message structure.
    PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, PTHREAD_COND_INITIALIZER, 0, 0
    };

#endif

#if(defined PTHREAD_COM_EXAMPLE)
#define DEBUG
// wait for sending thread to signal variable is ready to read
int timedWaitForCond(pthread_cond_t* pCond, pthread_mutex_t* pMutex, int timeToWait)
{
    gettimeofday(&now,NULL);
    timeout.tv_sec = now.tv_sec   timeToWait;
    timeout.tv_nsec = now.tv_usec * 1000;
    int retcode = 0;

    while((msg.testVal == 0) && (retcode != ETIMEDOUT))
    {
        // this unlocks the mutux while waiting on signal from another thread
 //       retcode = pthread_cond_timedwait(pCond, pMutex, &timeout);
        retcode = pthread_cond_wait(pCond,pMutex);
        // upon return the mutex is locked again
        std::cout << "exit pthread_cond_timedwait, testVal: " << msg.testVal << " value: " << msg.value << std::endl;
        if(retcode != 0)
        {
            switch(retcode)
            {
            case ETIMEDOUT: // exits while loop
                std::cout << "Timed Conditional Wait, Timed Out" << std::endl;
                return(-1); // conditioned variable is not ready yet
                break;
            default:        // stays in while loop
                std::cout << "Unexpected timed conditional wait result: " << retcode << std::endl;
                break;
            }
        }else
        {
            return(0);  // conditioned variable ready to read
            break;      // exit while if legitimate signal occurred
        }
    }
    return(0);
}

void outputMenu(void)
{
    std::cout << std::endl << std::endl;
    std::cout << "enter a message to send: " << std::endl;
    std::cout << " 0 - exit" << std::endl;
    std::cout << " 1 - message 1" << std::endl;
    std::cout << " 2 - message 2" << std::endl;
}
int getUserMessage(void)
{
    int rc;

    std::cin >> rc;
    return(rc);
}
/*
 * function messageReceiverThread():
 *      attempts to wait for message wait.  If result is good
 *      sends signal to waiting thread and then performs a
 *      timed conditioned wait to receive the message from the
 *      sending thread.
 *
 * algorithm:
 * input:     messageWait mutex
 * output:    character from message_t structure
 * memory:    shared message_t structure
 */
void*  messageReceiverThread(void* arg)
{
    int timeToWait = 10;        // use 10 second timeout for this example
    int rc = 0;

    // first, lock the mutex, to assure exclusive access to the conditioned variable
    rc = pthread_mutex_lock(&msg.mutex);
    if (rc)
    {   // an error has occurred
        std::cout << "messageReceiver: pthread_mutex_lock error: " << rc << std::endl;
        pthread_exit(NULL);
    }
    // mutex is now locked
    outputMenu();
    while(1)
    {
        msg.testVal = 0;    // clear signal value
        rc = timedWaitForCond( &msg.cond , &msg.mutex, timeToWait); // cond variable is unlocked while waiting
        // var is locked again after exit from timedWaitForCond()
        if(0 != rc)
        {
            // Timeout or error, no messasge yet, for this app just loop around...
            std::cout << "no message yet, rc = " << rc << std::endl;
        }else
        {
            // return from timed wait with valid signal from sender
            std::cout << msg.value << std::endl;
            std::cout << "messageReceiverThread msg.value received: " << msg.value << std::endl;
            switch(msg.value)
            {
            case MSG1:
                std::cout << message1 << std::endl;
                break;
            case MSG2:
                std::cout << message2 << std::endl;
                break;
            case MSGX:
                std::cout << "exiting" << std::endl;
                pthread_exit(arg);
                break;
            default:
                std::cout << "unrecognized message" << std::endl;
                break;
            }
            outputMenu();
        }
    }
    pthread_exit(arg);
}

/*
 * function messageSenderThread():
 *      attempts to wait for user to enter a number at the console.
 *      When the number is received this thread will attempt a lock
 *      on the message mutex and, once obtained, write the value
 *      to the msg.msgNbr.  Then this thread will send a signal to
 *      the messageReceiver thread. Once completed this thread will
 *      again wait on the user for an additional imput, until the
 *      value enterd by the user is 0, which will cause an exit.
 *
 * algorithm:
 * input:     messageWait mutex
 * output:    character from message_t structure
 * memory:    shared message_t structure
 */
void* messageSenderThread(void* arg)
{
    int rc = 0,msgCode = 0;

    while(1)
    {
        rc = pthread_mutex_lock(&msg.mutex);
        if(rc)
        {
            std::cout << "messageSender: lock request failed,result: " << rc << std::endl;
            pthread_exit(arg);
        }
        msgCode = getUserMessage();
        msg.value = msgCode;
        msg.testVal = 1;
        std::cout << "messageSender: msg.value: " << msg.value << std::endl;
        // signal the condition variable - there's a new message to handle
        rc = pthread_cond_signal(&msg.cond);
        std::cout << "messageSender: signal sent! " << std::endl;
        if(rc)
            std::cout << "messageSender: pthread_cond_signal failed,result: " << rc << std::endl;
        // unlock mutex
        rc = pthread_mutex_unlock(&msg.mutex);
        if(rc)
        {
            std::cout << "messageSender: unlock request failed,result: " << rc << std::endl;
            pthread_exit(arg);
        }

        if(!msgCode)            // user entered the exit code
            pthread_exit(arg);
    }
    pthread_exit(arg);
}

#undef DEBUG
#endif

int main(int argc, char *argv[])
{
#if(defined PTHREAD_COM_EXAMPLE)
    int        thr_id[NUM_HANDLER_THREADS];     // thread IDs
    pthread_t  thread1,thread2;  // thread's structures

    // create the message handling threads
    thr_id[0] = 0;
    (void) pthread_create(&thread1, NULL, messageReceiverThread, (void*)&thr_id[0]);
    thr_id[1] = 1;
    (void) pthread_create(&thread2, NULL, messageSenderThread, (void*)&thr_id[1]);

    // now wait for threads to exit...
    (void) pthread_join(thread1, NULL);
    (void) pthread_join(thread2, NULL);

    return 0;

#endif
}

CodePudding user response:

The problem here is starvation. Your messageSenderThread() function keeps the mutex locked practically all the time. Each time it releases the mutex at the bottom of the loop, the very next thing it does is (if you don't type "0") it re-locks the mutex back at the top of the loop. The messageReceiverThread() always loses the race to lock the mutex.

The reason why the message receiver does print something after you enter the "quit" command (0), is because the messageSenderThread() unlocks the mutex and it exits after you enter a zero. That finally allows the receiver to lock the mutex and do its thing.

  • Related