multithreadingpthreadsmutex

Why would we want to make a function recursive when it has a mutex lock?


https://stackoverflow.com/a/5524120/462608

If you want to call functions recursively, which lock the same mutex, then they either have to use one recursive mutex, or
have to unlock and lock the same non-recursive mutex again and again (beware of concurrent threads!), or
have to somehow annotate which mutexes they already locked (simulating recursive ownership/mutexes).

Can in any case this be a "sensible" design decision to make function recursive which already has a mutex lock?


Solution

  • In addition to paxdiablo's exmaple using an actual recursive funciotn, don't forget that using a mutex recursively doesn't necessarily mean that the functions involved are recursive. I've found use for recursive mutexes for dealing with a situation where you have complex operations which need to be atomic with respect to some data structure, with those complex operations rely on more fundamental operations that still need to use the mutex since the fundamental operations can be used on their own as well. An example might be something like the following (note that the code is illustrative only - it doesn't use proper error handling or transactional techniques that might really be necessary when dealing with accounts and logs):

    struct account
    {
        mutex mux;
    
        int balance;
    
        // other important stuff...
    
        FILE* transaction_log;
    };
    
    void write_timestamp( FILE*);
    
    
    // "fundamental" operation to write to transaction log
    void log_info( struct account* account, char* logmsg)
    {
        mutex_acquire( &account->mux);
    
        write_timestamp( account->transaction_log);
        fputs( logmsg, account->transaction_log);
    
        mutex_release( &account->mux);
    }
    
    
    // "composed" operation that uses the fundamental operation.
    //  This relies on the mutex being recursive
    void update_balance( struct account* account, int amount)
    {
        mutex_acquire( &account->mux);
    
        int new_balance = account->balance + amount;
    
        char msg[MAX_MSG_LEN];
        snprintf( msg, sizeof(msg), "update_balance: %d, %d, %d", account->balance, amount, new_balance);
    
        // the following call will acquire the mutex recursively
        log_info( account, msg);
    
        account->balance = new_balance;
    
        mutex_release( &account->mux);
    }
    

    To do something more or less equivalent without recursive mutexes means that the code would need to take care not to reacquire the mutex if it already held it. One option is to add some sort of flag (or thread ID) to the data structure to indicate if the mutex is already held. In this case, you're essentially implementing recursive mutexes - a trickier bit of work than it might seem at first to get right. An alternative is to pass a flag indicating you already acquired the mutex to functions as a parameter (easier to implement and get right) or simply have even more fundamental operations that assume the mutex is already acquired and call those from the higher level functions that take on the responsibility of acquiring the mutex:

    // "fundamental" operation to write to transaction log
    //  this version assumes that the lock is already held
    static
    void log_info_nolock( struct account* account, char* log msg)
    {
        write_timestamp( account->transaction_log);
        fputs( logmsg, account->transaction_log);
    }
    
    
    // "public" version of the log_info() function that
    //      acquires the  mutex
    void log_info( struct account* account, char* logmsg)
    {
        mutex_acquire( &account->mux);
        log_info_nolock( account, logmsg);    
        mutex_release( &account->mux);
    }
    
    
    // "composed operation that uses the fundamental operation
    //      since this function acquires the mutex, it much call the
    //      "nolock" version of the log_info() function
    void update_balance( int amount)
    {
        mutex_acquire( &account->mux);
    
        int new_balance = account->balance + amount;
    
        char msg[MAX_MSG_LEN];
        snprintf( msg, sizeof(msg), "update_balance: %d, %d, %d", account->balance, amount, new_balance);
    
        // the following call assumes the lock is already acquired
        log_info_nolock( account, msg);
    
        account->balance = new_balance;
    
        mutex_release( &account->mux);
    }