Я использую 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, пожалуйста.
Благодарю.
Как отмечает Ками Казе в комментариях, если это ваш полный пример, то это не проблема: есть только один путь, ведущий к get2
и этот путь уже приобретает мьютекс; просто не покупайте его во второй раз.
Однако в целом можно подумать о сценариях, где это не так ясно. В этом случае вы можете сделать мьютекс рекурсивный / возвратный:
В информатике реентрантный мьютекс (рекурсивный мьютекс, рекурсивная блокировка) — это особый тип устройства взаимного исключения (мьютекса), который может блокироваться несколько раз одним и тем же процессом / потоком, не вызывая взаимоблокировку.
В ваших настройках это будет через pthread_mutexattr_settype
:
pthread_mutexattr_settype(&m_lock, PTHREAD_MUTEX_RECURSIVE);
С этим:
pthread_mutex_lock(&m_lock);
get2();
pthread_mutex_unlock(&m_lock);
вы заперли весь get2()
, Так что нет смысла брать так же заблокировать снова внутри get2()
функция.
Просто удалите код блокировки и разблокировки из get2()
,
Если только часть кода в get2()
требует блокировки, а затем избавиться от блокировки и разблокировки от get1()
функция.
Например, в Java этот случай никогда не может произойти, когда вы используете
synchornized.
В вашем коде есть синхронизированный регионы не связаны между собой. Итак, для аналогичного сравнения вам нужно использовать другой мьютекс в 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
}