Home > OS >  How to create accurate nanosecond delay in a pthread and how to run a pthread part of the program wi
How to create accurate nanosecond delay in a pthread and how to run a pthread part of the program wi

Time:12-20

I am a beginner in C programming. In the following code, we have two pthreads. I want one of them to be delayed at the user's choice after the two pthreads are synchronized. I want this delay to be as accurate as possible. In the following code I have done this but the exact amount of delay does not occur.

But I also have another question, and that is how can I force a pthread to run a certain part of the program from start to finish without interruption.

Thank you in advance.

code:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/random.h>
#include <sys/time.h>
#include <math.h>

pthread_cond_t cond;
pthread_mutex_t cond_mutex;
unsigned int waiting;
struct timeval timeZero, timeOne, timeZeroBase, timeOneBase;
struct timespec tim, tim2;
int flag = 0;

void synchronize(void) {
    pthread_mutex_lock(&cond_mutex);
    if (  waiting == 2) {
        pthread_cond_broadcast(&cond);
    } else {
        while (waiting != 2)
            pthread_cond_wait(&cond, &cond_mutex);
    }
    pthread_mutex_unlock(&cond_mutex);
}

void *threadZero(void *_) {
    // ...
    synchronize();
    gettimeofday(&timeZeroBase, 0);
    if(flag == 0)
        nanosleep(&tim, &tim2);
    gettimeofday(&timeZero, 0);
    timeZero.tv_usec = timeZero.tv_usec - timeZeroBase.tv_usec;
    // ...
    return NULL;
}


void *threadOne(void *_) {
    // ...
    synchronize();
    gettimeofday(&timeOneBase, 0);
    if(flag == 1)
        nanosleep(&tim, &tim2);
    gettimeofday(&timeOne, 0);
    timeOne.tv_usec = timeOne.tv_usec - timeOneBase.tv_usec;
    // ...
    return NULL;
}


int main(void) {
    pthread_t zero, one;
    tim.tv_sec  = 0;
    tim.tv_nsec = 50;
    printf("Declare the number of function (0 or 1): ");
    scanf("%d", &flag);
    pthread_create(&zero, NULL, threadZero, NULL);
    pthread_create(&one, NULL, threadOne, NULL);
    // ...
    pthread_join(zero, NULL);
    pthread_join(one, NULL);
    printf("\nReal delay (ns): %lu\n", (timeZero.tv_usec - timeOne.tv_usec));
    return 0;
}

CodePudding user response:

One way that may increase accuracy is to busy-wait instead of sleeping.

I've made a function called mysleep that takes a struct timespec* containing the requested sleep time. It checks the current time and adds the requested sleep time to that - and then just spins until the current time >= the target point in time.

Note though: It's not guaranteed to stay within any accuracy. It will often be rather ok, but sometimes when the OS puts the thread on hold, you'll see spikes in the measured time. If you are unlucky, the calibration will have one of these spikes in it and then all your sleeps will be totally off. You can run the calibration routine 100 times and then pick the median value to make that unfortunate circumstance very unlikely.

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

static long calib; // used for calibrating mysleep()

void mysleep(const struct timespec *req) {
    struct timespec tp, now;

    clock_gettime(CLOCK_MONOTONIC, &tp); // get current time point

    // add the requested sleep time and remove the calibrated value
    tp.tv_sec  = req->tv_sec;
    tp.tv_nsec  = req->tv_nsec - calib;

    if(tp.tv_nsec > 999999999) {
        tp.tv_nsec -= 1000000000;
          tp.tv_sec;
    } else if(tp.tv_nsec<0) {
        tp.tv_nsec  = 1000000000;
        --tp.tv_sec;
    }

    // busy-wait until the target time point is reached: 
    do {
        clock_gettime(CLOCK_MONOTONIC, &now);
    } while(now.tv_sec < tp.tv_sec ||
            (now.tv_sec == tp.tv_sec && now.tv_nsec < tp.tv_nsec));
}

struct timespec get_diff(const struct timespec *start, struct timespec *end) {
    struct timespec temp;
    if((end->tv_nsec - start->tv_nsec) < 0) {
        temp.tv_sec = end->tv_sec - start->tv_sec - 1;
        temp.tv_nsec = 1000000000   end->tv_nsec - start->tv_nsec;
    } else {
        temp.tv_sec = end->tv_sec - start->tv_sec;
        temp.tv_nsec = end->tv_nsec - start->tv_nsec;
    }
    return temp;
}

// A non-scientific calibration routine
void calibrate() {
    struct timespec start, end, sleep = {0};
    calib = 0;

    clock_gettime(CLOCK_MONOTONIC, &start);
    mysleep(&sleep);
    clock_gettime(CLOCK_MONOTONIC, &end);
    struct timespec diff = get_diff(&start, &end);
    calib = (diff.tv_sec * 1000000000   diff.tv_nsec) / 2;
}

int main() {
    calibrate(); // must be done before using mysleep()

    // use mysleep()
}

Demo

Possible output (with a spike):

calib=157
should be close to 1000:  961
should be close to 1000:  931
should be close to 1000:  906
should be close to 1000:  926
should be close to 1000:  935
should be close to 1000:  930
should be close to 1000:  916
should be close to 1000:  932
should be close to 1000:  124441
should be close to 1000:  911
  • Related