Обработка исключений C ++ в критической секции (pthreads) - PullRequest
2 голосов
/ 07 января 2010

[Редактировать: (скопировано из комментария) Как выясняется, проблема была в другом месте, но спасибо всем за ваш вклад.]

У меня есть общий контейнерный класс, который использует один мьютекс для блокировки функций push () и pop (), поскольку я не хочу одновременно изменять голову и хвост. Вот код:

int Queue::push( WorkUnit unit )
{
    pthread_mutex_lock( &_writeMutex );
    int errorCode = 0;

    try
    {
        _queue.push_back( unit );
    }
    catch( std::bad_alloc )
    {
        errorCode = 1;
    }

    pthread_mutex_unlock( &_writeMutex );

    return errorCode;
}

Когда я запускаю это в режиме отладки, все выглядит превосходно. Когда я работаю в режиме выпуска, я получаю сбои примерно в то время, когда программа драйвера начинает нажимать и высовывать «одновременно». Вызывает ли блок try / catch немедленный выход, если он перехватывает исключение std :: bad_alloc? Если да, должен ли я заключить оставшуюся часть функции в блок finally?

Кроме того, возможно ли, что более медленный режим отладки будет успешным только потому, что мои вызовы push () и pop () фактически никогда не происходят одновременно?

Ответы [ 8 ]

8 голосов
/ 07 января 2010

В C ++ мы используем Resource Acquisition Is Initialization (RAII) для защиты от исключений.

5 голосов
/ 07 января 2010

Это действительно бомбардировка после исключения? Скорее всего, из вашего фрагмента кода у вас просто плохая синхронизация. Это начинается с названия вашего мьютекса: «writeMutex». Это не будет работать, если есть также «readMutex». Все операции чтения, просмотра и записи должны быть заблокированы одним и тем же мьютексом .

2 голосов
/ 07 января 2010

Блокирует ли попытка / отлов сразу заставить выход, если он ловит исключение std :: bad_alloc?

Нет. Если std :: bad_alloc выдается в блоке try {...}, код в блоке catch {...} сработает.

Если ваша программа действительно аварийно завершает работу, то похоже, что либо ваш вызов push_back вызывает какое-то исключение, отличное от bad_alloc (которое не обрабатывается в вашем коде), либо bad_alloc генерируется где-то за пределами try {...} блок.

Кстати, вы действительно хотите использовать блок try ... catch здесь?

1 голос
/ 18 февраля 2010

Предыдущий пример кода с классом Locker имеет большую проблему: Что вы делаете, когда и если pthread_mutex_lock () не работает? Ответ в том, что вы должны выбросить исключение из конструктора, и оно может быть перехвачено. Хорошо. Тем не мение, В соответствии со спецификациями исключений c ++ создание исключения из деструктора - нет-нет. КАК ВЫ ОБРАЩАЕТЕ pthread_mutex_unlock ОТКАЗЫ?

1 голос
/ 07 января 2010

Вам нужно использовать RAII
В основном это означает использование конструктора / деструктора для блокировки / разблокировки ресурса.
Это гарантирует, что мьютекс всегда будет разблокирован, даже когда есть исключения.

Вы должны использовать только один мьютекс для доступа к списку.
Даже если у вас есть мьютекс только для чтения, который используется потоком, который только читает. Это не означает, что чтение безопасно, когда другой поток обновляет очередь. Очередь может находиться в каком-то промежуточном состоянии, вызванном потоком, вызывающим push (), в то время как другой поток пытается перейти в промежуточное состояние invlide.

class Locker
{
    public:
        Locker(pthread_mutex_t &lock)
            :m_mutex(lock)
        {
            pthread_mutex_lock(&m_mutex);
        }
        ~Locker()
        {
            pthread_mutex_unlock(&m_mutex);
        }
    private:
        pthread_mutex_t&    m_mutex;
};

int Queue::push( WorkUnit unit )
{
    // building the object lock calls the constructor thus locking the mutex.
    Locker  lock(_writeMutex);
    int errorCode = 0;

    try
    {
        _queue.push_back( unit );
    }
    catch( std::bad_alloc )  // Other exceptions may happen here.
    {                        // You catch one that you handle locally via error codes. 
        errorCode = 1;       // That is fine. But there are other exceptions to think about.
    }

    return errorCode;
}  // lock destructor called here. Thus unlocking the mutex.

PS. Я ненавижу использование ведущих подчеркивания.
Хотя технически здесь все в порядке (при условии использования переменных-членов), это так легко испортить, что я предпочитаю не ставить перед идентификаторами «». См. Каковы правила использования подчеркивания в идентификаторе C ++? для полного списка правил, касающихся '' в именах идентификаторов.

1 голос
/ 07 января 2010

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

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

1 голос
/ 07 января 2010

plus

как выглядит pop

создает класс оболочки блокировки, который автоматически освобождает блокировку, когда она выходит из области видимости (как в комментарии RAII)

c ++ не имеет окончательно (благодаря тому, что мистер Стоустроп был дрянным)

я бы поймал std :: исключение или его вообще не было (утки направляются вниз в войну пламенем).Если вы ничего не поймали, тогда вам нужна оболочка

0 голосов
/ 18 февраля 2010

Запуск кода под любым программным обеспечением не имеет никакого значения. Вы должны исправить код, который работает, а не запускать его под valgrind.

В Си все отлично работает:

pthread_cleanup_pop( 0 );
r = pthread_mutex_unlock( &mutex );
if ( r != 0 )
{
    /* Explicit error handling at point of occurance goes here. */
}

Но поскольку c ++ - программный аборт, просто нет разумного способа с какой-либо степенью уверенности справляться с поточно-закодированными сбоями. Идеи мертвого мозга, такие как обертывание pthread_mutex_t в класс, который добавляет какую-то переменную состояния, - это просто - мозг мертв. Следующий код просто не работает:

Locker::~Locker()
{
    if ( pthread_mutex_unlock( &mutex ) != 0 )
    {
        failed = true; // Nonsense.
    }
}

И причина этого в том, что после того, как pthread_mutex_unlock () вернет этот поток, он может быть нарезан из процессора с вытеснением. Это означает, что публичная переменная .failed будет по-прежнему ложной. Другие потоки, просматривающие его, получат неверную информацию - переменная состояния сообщает об отсутствии ошибок, а pthread_mutex_unlock () - нет. Даже если по счастливой случайности эти два оператора выполняются за один раз, этот поток может быть прерван до того, как ~ Locker () вернется, а другие потоки могут изменить значение .failed. В итоге эти схемы не работают - не существует атомарного механизма тестирования и установки для переменных, определенных приложением.

Некоторые говорят, что у деструкторов никогда не должно быть кода, который дает ошибку. Все остальное - плохой дизайн. Хорошо. Мне просто любопытно посмотреть, что такое хороший дизайн, чтобы быть 100% -ным исключением и поточно-ориентированным в c ++.

...