简体   繁体   中英

pthread_mutex_lock how to not lock when it is the same thread

I'm using pthread_mutex_t for locking.

pthread_mutex_t m_lock;

void get1() {
    cout<<"Start get 1"<<endl;
    pthread_mutex_lock(&m_lock);
    get2();
    pthread_mutex_unlock(&m_lock);
    cout<<"End get 1"<<endl;
}

void get2() {
    cout<<"Start get 2"<<endl;
    pthread_mutex_lock(&m_lock); // The program actually stops here because it waits to m_lock to be unlock from get1 function.
    pthread_mutex_unlock(&m_lock);
    cout<<"End get 2"<<endl;
}

// The thread call to run function
void* run(void* p) {
    get1();
}

Lets say I have only one thread that calls to run function, so: get1 lock the m_lock and call to get2, but when it tries to lock m_lock, it waits that the lock will be unlock (something that not happen) and we got a deadlock.

My question is how can I avoid this case when the same thread that locked the lock in get1, will not need to wait for the lock in get2 (because it is the same thread)?

For example, in Java this case never can happen when you use synchornized.

public Test implements Runnable {
    public void get1() {
        System.out.println("Start get 1");
        synchronized (this) {
            get2();
        }
        System.out.println("End get 1");
    }

    public void get2() {
        System.out.println("Start get 2");
        synchronized (this) {

        }
        System.out.println("End get 2");
    }

    @Override
    public void run() {
        get1();
    }
}

No deadlock here.

I want the same result in my C code please.

Thanks.

As noted by Kami Kaze in the comments, if this is your full example, then it's a non-issue: there's only one path leading to get2 , and this path already acquires the mutex; simply omit acquiring it a second time.

However, in general, it's possible to think of scenarios where it's not that clear. In this case, you can make the mutex recursive/reentrant :

In computer science, the reentrant mutex (recursive mutex, recursive lock) is particular type of mutual exclusion (mutex) device that may be locked multiple times by the same process/thread, without causing a deadlock.

In your settings, this would be via pthread_mutexattr_settype :

pthread_mutexattr_settype(&m_lock, PTHREAD_MUTEX_RECURSIVE);

With this:

pthread_mutex_lock(&m_lock);
get2();
pthread_mutex_unlock(&m_lock);

you have locked the entire get2() . So, there's no point in taking the same lock again inside get2() function. Just remove the locking and unlocking code from get2() .

If only the code part in get2() requires a locking then get rid of the locking and unlocking from get1() function.

For example, in Java this case never can happen when you use synchornized.

In your code there the synchronized regions are not interlinked. So, for a similar comparison, you need to use a different mutex in get2() function.

This is called lock recursion.

The last argument to pthread_mutex_init is an attributes struct. You can set the attributes to allow recursive locking with pthread_mutexattr_settype(..., PTHREAD_MUTEX_RECURSIVE) .

But, I must add some editorial content here. I believe very strongly that lock recursion is almost always a bug. Or it will lead to impossible to debug bugs later in the programs life time.

A locking operation can be reasoned to mean "when the lock function returns the object protected by the lock is in a known state and this state will not change until the unlock function is called". This means that if get1 has started to modify the object you protect with the lock and then get2 recurses that lock, this contract is broken twice. First because get2 succeeds obtaining the lock while the object is not in a known state, second because the object is modified while get1 thinks it owns the lock.

Sure, we often get away with doing things like this but it is a terrible practice. Redesign your program to not recurse locks. The standard way to do this would be to implement a function called get2_locked and get2 obtains the lock and calls get2_locked while get1 already knows it has the lock and would call get2_locked .

I assume that get1 really does more than just acquire the lock and call get2 ? Otherwise what is the point of get1 ?

If that's the case you could solve it by having a get3 function which does the main part of get2 (the part you don't show here) and which doesn't lock. Then call that new function from get1 instead (and of course from get too):

void get1()
{
    // Do something here

    cout<<"Start get 1"<<endl;
    pthread_mutex_lock(&m_lock);
    get3();  // <-- Note call get3 instead here
    pthread_mutex_unlock(&m_lock);
    cout<<"End get 1"<<endl;

    // Do something more here
}

void get2()
{
    cout<<"Start get 2"<<endl;
    pthread_mutex_lock(&m_lock); // The program actually stops here because it waits to m_lock to be unlock from get1 function.
    get3();  // <-- Note call to get3 here
    pthread_mutex_unlock(&m_lock);
    cout<<"End get 2"<<endl;
}

void get3()
{
    // Do the actual work of get2 here...
    // Note: No locking here
}

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