блокировка мьютекса 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
}