Блокировка мьютекса в деструкторе в C ++ 11

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

#include <mutex>
#include <thread>

std::mutex mutex;
int n=0;

class Counter{
public:
Counter(){
std::lock_guard<std::mutex>guard(mutex);
n++;}
~Counter(){
std::lock_guard<std::mutex>guard(mutex);//How can I protect here the underlying code to mutex.lock() ?
n--;}
};

void doSomething(){
Counter counter;
//Here I could do something meaningful
}

int numberOfThreadInDoSomething(){
std::lock_guard<std::mutex>guard(mutex);
return n;}

У меня есть мьютекс, который мне нужен для блокировки деструктора объекта. Проблема в том, что мой деструктор не должен бросать исключения.

Что я могу сделать ?

0) не могу заменить n с атомарной переменной (конечно, это бы сработало, но это не главное в моем вопросе)

1) я мог бы заменить свой мьютекс на спин-блокировку

2) Я мог бы попытаться перехватить блокировку в бесконечном цикле, пока в конце концов не получу блокировку без исключения

Ни одно из этих решений не кажется очень привлекательным. У вас была такая же проблема? Как ты это решил?

5

Решение

По предложению Адама Х. Петерсона я наконец решил написать мьютекс без бросков:

class NoThrowMutex{
private:
std::mutex mutex;
std::atomic_flag flag;
bool both;
public:
NoThrowMutex();
~NoThrowMutex();
void lock();
void unlock();
};

NoThrowMutex::NoThrowMutex():mutex(),flag(),both(false){
flag.clear(std::memory_order_release);}

NoThrowMutex::~NoThrowMutex(){}

void NoThrowMutex::lock(){
try{
mutex.lock();
while(flag.test_and_set(std::memory_order_acquire));
both=true;}
catch(...){
while(flag.test_and_set(std::memory_order_acquire));
both=false;}}

void NoThrowMutex::unlock(){
if(both){mutex.unlock();}
flag.clear(std::memory_order_release);}

Идея состоит в том, чтобы иметь два мьютекса вместо одного. Настоящий мьютекс — это спин-мьютекс, реализованный с std::atomic_flag, Этот спиновый мьютекс защищен std::mutex который мог бы бросить.

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

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

В худшем случае этот механизм блокировки ухудшается до блокировки вращения. Большую часть времени он реагирует как обычный мьютекс.

8

Другие решения

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

Было бы идеально, если бы код мог быть реструктурирован таким образом, чтобы деструктор не делал ничего, что не может дать сбой. Однако, если ваш код в противном случае корректен, сбой при получении блокировки, вероятно, встречается редко, за исключением случаев ограниченных ресурсов, так что поглощение или прерывание при сбое вполне может быть приемлемым. Для некоторых мьютексов lock (), вероятно, является операцией без бросков (такой как спин-блокировка с использованием atomic_flag), и если вы можете использовать такой мьютекс, вы можете ожидать, что lock_guard никогда не сгенерирует. Ваше единственное беспокойство в этой ситуации — тупик.

4

По вопросам рекламы [email protected]