João Marcelino
João Marcelino

Reputation: 33

How does the pthread_cond_timedwait() works?

So I was trying to understand how the pthread_cond_timedwait() worked, because I was having some problems doing synchronization on my project. This was the code i came up with, but it doens't work as I thought it would. My objective was to print the time, wait for 2 seconds, then print the time again to see the passing of time.

//gcc -Wall -pthread timedwait.c -o  exe


#define _OPEN_THREADS                                                           
#include <pthread.h>                                                            
#include <stdio.h>                                                              
#include <time.h>                                                               
#include <errno.h>
#include <stdlib.h>




int main() {                                                                        
  pthread_cond_t cond;                                                          
  pthread_mutex_t mutex;                                                        
  time_t T;                                                                     
  struct timespec t;                                                            

  if (pthread_cond_init(&cond, NULL) != 0) {                                    
    perror("pthread_cond_init() error");                                        
    exit(2);                                                                    
  }                                                                             


  time(&T);                                                                     
  t.tv_sec = T + 2;                                                             
  printf("starting timedwait at %s", ctime(&T));                                
  pthread_cond_timedwait(&cond, &mutex, &t);                                                                                               
  time(&T);                                                                     
  printf("timedwait over at %s", ctime(&T));                                  
}

Upvotes: 3

Views: 3853

Answers (2)

John Bollinger
John Bollinger

Reputation: 180058

There are several problems with your code, the most important ones all having to do with using variables whose values are indeterminate.

Your other answer discusses the fact that you fail to initialize some of the members of your structure t, which is, indeed, a significant flaw. When I modify your program to capture the return value of the pthread_cond_timedwait call, I find that it returns EINVAL, indicating an invalid argument. There are two good alternatives for resolving that problem:

  • Declare that variable with an initializer:

    struct timespec t = { 0 };
    // ...
    t.tv_sec = T + 2;
    

    That particular initializer sets the first member to zero explicitly, and all other members are set to default (zero) values implicitly -- a characteristic of initialization that does not apply to member-by-member assignment. Not only is this more convenient than assigning each member explicitly, but it also takes care of any undocumented members that your particular implementation's version of that structure type may have.

  • OR set a value for the (whole) structure via a function that serves the purpose. For example,

    struct timespec t;
    clock_gettime(CLOCK_REALTIME, &t);
    // ... no need for T or time() ...
    t.tv_sec += 2;
    

    This requires a function (such as clock_gettime) that does not rely on the original value of the structure.

After correcting the initialization problem for variable t, the resulting program still fails for me, but with a different error: EPERM, indicating that the operation is not permitted for the particular combination of arguments. For this particular function, that would be because the mutex is not locked by the calling thread at the time of the call, but it's actually worse than that: the mutex is not even initialized. One could initialize it via pthread_mutex_init, but if you don't care to set any non-default attributes then the static initializer is more convenient:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int result;
// ...
result = pthread_mutex_lock(&mutex);

(Note: there is a static initializer for condition variables, too.)

Additionally, you do not consistently check the return values of your function calls, as I observed in comments. It's ok to omit such checks where you don't care about the success or effect of the call, or at least where it's reasonable to ignore any failure that may occur, but where you depend on a particular result for the subsequent behavior of your program to be correct, it is an essential matter of safe programming to check the return value.

Finally, as a minor matter, GCC and GLibc attribute no significance to the _OPEN_THREADS feature-test macro, and it is not defined by POSIX. It appears to be specific to IBM's toolchain. It probably is not harmful in your case, but it is definitely out of place and unhelpful, and even in an environment to which it applies there appear to be better ways to get the same effects it provides.

This variation on your program addresses all those issues:

// gcc -Wall -pthread timedwait.c -o  exe

#include <pthread.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

int main() {
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    struct timespec t;
    int result;

    result = pthread_mutex_lock(&mutex);
    if (result != 0) {
        fprintf(stderr, "pthread_mutex_lock: %s\n", strerror(result));
        exit(EXIT_FAILURE);
    }

    result = clock_gettime(CLOCK_REALTIME, &t);
    if (result == -1) {
        perror("clock_gettime");
        exit(EXIT_FAILURE);
    }

    // Return-value check is non-essential here:
    printf("starting timedwait at %s", ctime(&t.tv_sec));

    t.tv_sec += 2;
    result = pthread_cond_timedwait(&cond, &mutex, &t);
    if (result != ETIMEDOUT) {
        fprintf(stderr, "%s\n", strerror(result));
    }

    result = clock_gettime(CLOCK_REALTIME, &t);
    if (result == -1) {
        perror("clock_gettime");
        exit(EXIT_FAILURE);
    }

    // Return-value check is non-essential here:
    printf("timedwait over at %s", ctime(&t.tv_sec));

    // Return-value check is non-essential here, because we'll just exit anyway:
    pthread_mutex_unlock(&mutex);
}

Lastly, I observe that in my own programming I typically define one or more macros to support return-value checking. I find that using such a macro instead of explicitly writing code for each check makes the overall flow of the program clearer, not to mention saving me keystrokes. I do not demonstrate that in the above, though the usefulness of that approach may already be evident even in such a short program.

Upvotes: 2

Steve Friedl
Steve Friedl

Reputation: 4247

If what you're seeing is that the wait completes more or less immediately rather than waiting for 2 seconds as requested, then this is almost certainly because the timespec object contains junk.

The tv_sec contains the number of seconds, of course, but tv_nsec contains nanoseconds, and it might be easy to dismiss this by saying: I don't care if it's a few nanoseconds more or less than 2 seconds.

But that's not how it works. If the junk on the stack happens to be out of range, it will fail. Let's see how by intentionally filling tv_nsec with junk (a negative number):

// gcc -Wall -pthread timedwait.c -o exe        <-- thank you for this

#define _OPEN_THREADS
#include <pthread.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

int main() {
  pthread_cond_t cond;
  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  time_t T;
  struct timespec t;

  if (pthread_cond_init(&cond, NULL) != 0) {
    perror("pthread_cond_init() error");
    exit(2);
  }

  time(&T);
  t.tv_sec = T + 5;
  t.tv_nsec = -1;  // INTENTIONAL JUNK

  printf("starting timedwait at %s", ctime(&T));
  int err = pthread_cond_timedwait(&cond, &mutex, &t);
  if (err != 0  &&  err != ETIMEDOUT)
  {
    printf("pthread_cond_timeout failed: %s\n", strerror(err));
    exit(EXIT_FAILURE);
  }
  time(&T);
  printf("timedwait over at %s", ctime(&T));

  return 0;
}

When I run this - with T + 5 to be really sure I can see the wait - it fails immediately:

$ ./exe
starting timedwait at Fri Nov 29 19:50:52 2019
pthread_cond_timedwait failed: Invalid argument

But changing to t.tv_nsec = 0 makes it wait the time you expect.

EDIT Added specific error checking to pthread_cond_timeout (with h/t to @JohnBollinger)

EDIT Added a test for ETIMEDOUT

Upvotes: 2

Related Questions