Почему этот нерекурсивный мьютекс можно получить дважды? - PullRequest
0 голосов
/ 04 июля 2018

Я только недавно узнал о переменных условия pthread, что, по-видимому, является фундаментальным для этого вопроса.

Я наблюдаю за тем, что поток «прорывается» и приобретает мьютекс, принадлежащий другому потоку!

Это разрушает основы моего понимания владения мьютексом, и я не знаю, как это объяснить:

В следующем коде у меня есть class ScopeLock, довольно распространенная оболочка C ++ поверх мьютекса, которая получает мьютекс в своем ctor и освобождает его в своем dtor.

Начиная с main(), я создаю два потока, каждый из которых пытается получить общий мьютекс. Поскольку между созданием двух потоков существует здоровый сон, ожидается, что первый порожденный поток получит мьютекс.

В потоке 1 я делаю pthread_cond_wait() и никогда не сигнализирую переменную условия, намереваясь заблокировать навсегда.

Намерение состоит в том, что, поскольку поток 1 получает мьютекс и блокируется навсегда, поток 2 также будет блокироваться навсегда, когда он попытается получить мьютекс.

Код:

// main.cpp

#include <iostream>
#include <pthread.h>
#include <unistd.h>

class ScopeLock
{
public:

  ScopeLock( pthread_mutex_t& mutex ) : mutex_( mutex )
  {
    pthread_mutex_lock( &mutex );
  }

  ~ScopeLock()
  {
    pthread_mutex_unlock( &mutex_ );
  }

private:

  pthread_mutex_t mutex_;
};

pthread_mutex_t g_mutex;
pthread_cond_t g_cond;

void* func1( void* arg )
{
  std::cout << "locking g_mutex from " << pthread_self() << std::endl;
  ScopeLock lock( g_mutex );
  std::cout << "locked g_mutex from " << pthread_self() << std::endl;

  std::cout << __FUNCTION__ << " before cond_wait()" << std::endl;
  pthread_cond_wait( &g_cond, &g_mutex );
  //sleep( 1000 );
  std::cout << __FUNCTION__ << " after cond_wait()" << std::endl;
  return NULL;
}

void* func2( void* arg )
{
  std::cout << "locking g_mutex from " << pthread_self() << std::endl;
  ScopeLock lock( g_mutex );
  std::cout << "locked g_mutex from " << pthread_self() << std::endl;

  std::cout << __FUNCTION__ << std::endl;
  return NULL;
}

int main( int argc, char* argv[] )
{
  pthread_t t1;
  pthread_t t2;

  pthread_mutex_init( &g_mutex, NULL );
  pthread_cond_init( &g_cond, NULL );

  pthread_create( &t1, NULL, func1, NULL );

  sleep ( 2 );

  pthread_create( &t2, NULL, func2, NULL );

  pthread_join( t2, NULL );
  std::cout << "joined t2" << std::endl;
  pthread_join( t1, NULL );
  std::cout << "joined t1" << std::endl;

  return 0;
}

Компиляция / выход:

>g++ --version
g++ (GCC) 4.8.3 20140911 (Red Hat 4.8.3-7)
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

>g++ -g main.cpp -lpthread && ./a.out
locking g_mutex from 139707808458496
locked g_mutex from 139707808458496
func1 before cond_wait()
locking g_mutex from 139707800065792 // <-- Here onward is output 2 sec later
locked g_mutex from 139707800065792
func2
joined t2

Но выходные данные исполняемого файла показывают, что поток 2 продвигается дальше захвата мьютекса! Может кто-нибудь объяснить, почему это происходит?

Вы можете видеть, что я попытался проверить ситуацию с помощью "sleep( 1000 )": если я закомментирую pthread_cond_wait() и раскомментирую sleep(), тогда поведение исполняемого файла будет соответствовать моему ожиданию, что поток 2 не выходит за пределы оператора "locking mutex..." в func2().

Итак, я предполагаю, что «неожиданное» поведение этого приложения связано с pthread_cond_wait(), но, думаю, я принципиально не понимаю, почему: почему поток 2 может продвинуться дальше захвата мьютекса? Я ожидал, что поток 1, получив мьютекс и ожидая условной переменной, которая никогда не будет сигнализирована, заблокировал бы поток 2 от получения мьютекса - почему это не так?

Благодарен за помощь и объяснения от сообщества.

Edit:

Я начинаю формировать намек на идею ... Я помню кое-что о pthread_cond_wait() разблокировке его мьютекса, пока он ждет ... так что мне интересно, "отменяет" ли он мьютекс удержания ScopeLock ... ? У меня нет правильной / полностью сформированной идеи, поэтому я все еще могу использовать исчерпывающий ответ от знающих пользователей.

1 Ответ

0 голосов
/ 04 июля 2018

Это так, поскольку поток 1 получает мьютекс и блокирует навсегда, поток 2 также будет блокироваться навсегда, когда он попытается получить мьютекс.

С документация :

Эти функции атомарно освобождают мьютекс и заставляют вызывающий поток блокировать переменную условия cond;

Следовательно, поток 1 освобождает мьютекс, который поток2 с удовольствием использует.

Это нормально, хотя pthread_cond_wait повторно получает мьютекс перед возвратом, что делает ваше использование совершенно безупречным:

После успешного возвращения мьютекс должен быть заблокирован и принадлежит вызывающему потоку.

Этот вопрос может быть интересен для понимания, почему он работает таким образом: Почему для функций условных переменных в pthreads требуется мьютекс?

...