简体   繁体   中英

C: Thread synchronization using pthreads, locking and unlocking mutex

I'm working on a larger project including two threads, where one database is shared between them. One threads job is to just count down every active elements timer, when the timer for that element is zero it should set it inactive and zero it out.

The main thread is just manipulating the active element's other variables in some way.

I am not sure how to synchronize these threads, especially as the timer thread cleans up the database element when the timer reaches zero. Right now I believe that the time between unlocking and locking the mutex in the while(1) loop in main() may be too fast?

If the timer thread is waiting for the mutex to unlock at pthread_mutex_lock() ; and it unlocks in main() , do we know that the timer thread will be the next one to lock the mutex or might the main() be so fast that it locks it again as there is no task between pthread_mutex_unlock() and pthread_mutex_lock() ?

I have no experience using condition variables, may that be a good idea here?

Here is a minimal working example of my much larger project.

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

#define NUM_COMPUTER 10

typedef struct computer
{
    unsigned int active;
    unsigned int timer;
    int x;
}computer_t;

/**
 * Function declarations
 */
void activate(computer_t* comp);
void* timerFunction(void* args);

/**
 * Global variables
 */
computer_t database[NUM_COMPUTER];
pthread_mutex_t mutex;

main() function

/**
 * Main
 */
int main(void)
{
    memset(database, 0, sizeof database);

    // Initialize some database elements
    activate(database);
    database[0].x = 5;

    activate(database + 3);
    database[3].x = 23;

    activate(database + 9);
    database[9].x = -7; 

    pthread_t timer_thread;
    pthread_create(&timer_thread, NULL, timerFunction, NULL);

    while(1)
    {
        /**
         * MAY PROBLEMS OCCUR HERE? UNLOCKING AND LOCKING ALMOST DIRECTLY
         */
         
        // ************* LOCK *************
        pthread_mutex_lock(&mutex);

        /**
         * Manipulate struct members 
         */
        for (int i = 0; i < NUM_COMPUTER; i++)
        {
            if (database[i].active)
            {
                database[i].x += 1;
            }
        }
        // ************* UNLOCK *************
        pthread_mutex_unlock(&mutex);
    }

    return 0;
}

Extra functions

void activate(computer_t* comp)
{
    comp->active = 1;
    comp->timer = 100;
}

void* timerFunction(void* args)
{
    struct timespec ts;

    // Sleep interval before checking database again
    ts.tv_sec = 1;
    ts.tv_nsec = 0;

    while(1)
    {
        // ************* LOCK *************
        pthread_mutex_lock(&mutex);

        /**
         * Go through every database index
         */
        for (int index = 0; index < NUM_COMPUTER; index++)
        {   
            if (database[index].active)
            {
                if (database[index].timer > 0)
                {
                    database[index].timer--;
                }
                else
                {
                    /**
                     * Clean up database index
                     */
                    memset(database + index, 0, sizeof database);
                }
            }
        }

        // ************* UNLOCK *************
        pthread_mutex_unlock(&mutex);

        /**
         * Sleep 1 sec before checking database again
         */
        nanosleep(&ts, NULL);
    }
}

If the timer thread is waiting for the mutex to unlock at pthread_mutex_lock() ; and it unlocks in main() , do we know that the timer thread will be the next one to lock the mutex

No.

At least, not in general. This is question of thread scheduling policy, which your particular platform may or may not allow you to adjust. The pthreads API makes no guarantees in this area.

or might the main() be so fast that it locks it again as there is no task between pthread_mutex_unlock() and pthread_mutex_lock() ?

It is not only possible, but fairly likely that this happens at least some of the time.

I have no experience using condition variables, may that be a good idea here?

Condition variables are an important tool to have in your belt, but I see no reason to think that they would be a particular help here.

The main problem with the code you present is that both threads have to lock substantially the whole world for their own exclusive use whenever they are active. As such, even if all the locking worked as desired, you would have no true concurrency, hence multithreading is adding complexity without conferring any actual advantages.

If you want to continue in that mode, then I would drop the multithreading and instead set up a POSIX timer to raise a flag at one-second intervals (or whatever interval you prefer). Then your single-threaded program can check that flag at the top of each loop iteration to determine whether to perform its normal work or one pass of timeout management.

If you want true concurrency then you probably need to either shrink the critical regions guarded by your mutex, or change from a single mutex protecting everything to multiple mutexes, each protecting a subset of all the things. Perhaps you could alternatively rework things around atomic objects and operations instead of mutexes, but what I see so far does not give me much confidence in that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM