Search code examples
clinuxmultithreadingposixthreadpool

Linux C, How to schedule 10 waiting threads to execute in FIFO?


I'm using the following conditional thread to lock 10 threads using FIFO scheduling, I need to unlock the threads in the order with which they have arrived to pthread_cond_wait(&cvv, &lock); I need to know if that is possible or there is no guarantee to have this scenario.

struct sched_param param = {.sched_priority = 10};
pthread_cond_t cvv = (pthread_cond_t) PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;

void *thread(void *v) {
    pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
    int index = *((int *)v);
    printf("Locking [%d] and waiting\n", index);
    pthread_mutex_lock(&lock);
    pthread_cond_wait(&cvv, &lock);
    printf("Unlockd [%d] => %d\n", index, clock());
    pthread_mutex_unlock(&lock);
    return NULL;
}

int main(int argc, char** argv) {
    for (int i = 0; i < 10; i++) {
        t = (pthread_t *) malloc(sizeof (pthread_t));
        int *x = malloc(sizeof (int));
        *x = i;
        pthread_create(t, NULL, thread, x);
    }
    for (int i = 0; i < 10; i++) {
        pthread_cond_signal(&cvv);
    }
}

The out put is not in ascendant ordered

Locking [0] and waiting
Locking [1] and waiting
Locking [2] and waiting
Locking [3] and waiting
Locking [4] and waiting
Locking [5] and waiting
Locking [6] and waiting
Locking [7] and waiting
Locking [8] and waiting
Locking [9] and waiting

Unlocked [0] => 7043
Unlocked [8] => 7084
Unlocked [6] => 7100
Unlocked [2] => 7130
Unlocked [5] => 7294
Unlocked [7] => 7362
Unlocked [3] => 7407
Unlocked [1] => 7463
Unlocked [9] => 7482
Unlocked [4] => 7559

Solution

  • pthread_setschedparam is probably not available to you, as it requires privilege. You do not check the return values of these calls, but you will likely find that they are failing with EPERM. They do so for me.

    I need to unlock the threads in the order with which they have arrived to pthread_cond_wait(&cvv, &lock); I need to know if that is possible or there is no guarantee to have this scenario.

    Yes, you can do this, but you will need to roll your own. Here's one scheme that can accomplish it:

    • establish three global counters, say in, out, and next, access to which are protected by the mutex. Initialize all of them to 0.

    • Each thread passes through the lock like so:

      • before waiting on the CV it increments in and records the new value. This is its "ticket".
      • it tests whether its ticket is equal to next and next is less than or equal to out, and waits on the CV only if one or both of these conditions is not met.
      • if it does wait, then when it returns, it loops back to testing its ticket against the current value of next.
      • before it proceeds past the lock, it increments next.
      • finally, it broadcasts to the CV (this may be either before or after unlocking the mutex)
    • to unlock the next thread,

      • increment out
      • if next is still 0 then increment it, too
      • call pthread_cond_broadcast (not pthread_cond_signal)

    Of course, all accesses to in, out, and next must be performed under protection of the mutex, which should be the same one used with the CV.

    Note that that scheme permits threads to grant unlocks prospectively, when no threads are presently locked. That will actually solve one of the problems with your current implementation, but if you prefer, you can rework it using only in and next, making the thread performing the unlocking wait until in exceeds next. This can be achieved with use of the same CV. Details are left as an exercise.