блокировка мьютекса pthread как не блокировать, когда это тот же поток

Я использую pthread_mutex_t для блокировки.

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();
}

допустим, у меня есть только один поток, который вызывает функцию запуска, поэтому: get1 блокирует m_lock и вызывает get2, но когда он пытается заблокировать m_lock, он ждет, что блокировка будет разблокирована (что-то, что не произойдет), и мы получили тупик.

мой вопрос в том, как я могу избежать этого случая, когда тот же поток, который заблокировал блокировку в get1, не будет ждать блокировки в get2 (потому что это то же самое нить)?

например, в Java этот случай никогда не может произойти, когда вы используете 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();
    }
}

здесь нет тупика.

Я хочу тот же результат в моем коде C, пожалуйста.

спасибо.

4 ответов


как отметил Ками Кадзе в комментариях, если это полный пример, то это не проблема: есть только один путь, ведущий к get2, и этот путь уже приобретает мьютекс; просто опустите его приобретение во второй раз.

однако, в целом, можно подумать о сценариях, где это не так ясно. В этом случае, вы можете сделать мьютекс рекурсивный/реентерабельной:

в информатике реентрантный мьютекс (рекурсивный мьютекс, рекурсивный lock) - это особый тип устройства взаимного исключения (мьютекса), которое может быть заблокировано несколько раз одним и тем же процессом/потоком, не вызывая взаимоблокировки.

в настройках, это будет через pthread_mutexattr_settype:

pthread_mutexattr_settype(&m_lock, PTHREAD_MUTEX_RECURSIVE);

С этого:

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

вы заблокировали весь get2(). Так что нет смысла брать то же самое снова запереть внутри


это называется рекурсией блокировки.

последний аргумент pthread_mutex_init является структурой атрибутов. Вы можете установить атрибуты, позволяющие рекурсивную блокировку с помощью pthread_mutexattr_settype(..., PTHREAD_MUTEX_RECURSIVE).

но, я должен добавить некоторые редакционные материалы здесь. Я очень сильно верю, что рекурсия блокировки почти всегда является ошибкой. Или это приведет к невозможности отладки ошибок позже в течение жизни программ.

можно аргументировать, что операция блокировки означает "когда функция блокировки возвращает объект, защищенный блокировкой, находится в известном состоянии, и это состояние не изменится, пока не будет вызвана функция разблокировки". Это означает, что если get1 начал изменять объект, который вы защищаете с помощью блокировки, а затем get2 повторяется, что замок, этот договор будет прекращен в два раза. Во-первых, потому что get2 удается получить блокировку, пока объект не находится в известном состоянии, во-вторых, потому что объект изменяется, в то время get1 думает, что он владеет замком.

конечно, мы часто делать такие вещи, как это, но это ужасная практика. Перепроектируйте свою программу, чтобы не рекурсировать блокировки. Стандартный способ сделать это - реализовать функцию get2_locked и get2 получает блокировку и звонки get2_locked пока get1 уже знает, что у него есть замок и вызовет get2_locked.


Я предполагаю, что get1 действительно делает больше, чем просто получить блокировку и вызов get2? Иначе в чем смысл get1?

если это так, вы можете решить его, имея get3 функции, которая выполняет основную часть get2 (часть, которую вы здесь не показываете) и которая не блокируется. Затем вызовите эту новую функцию из get1 вместо этого (и, конечно, из get тоже):

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
}