list :: empty () многопоточное поведение? - PullRequest
9 голосов
/ 08 октября 2019

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

Ничего страшного, если звонок на list::empty() неправильный в 100% случаев. Я только хочу избежать сбоя или прерывания одновременных вызовов list::push() и list::pop().

Могу ли я предположить, что VC ++ и Gnu GCC только иногда ошибаются и не делают empty() и ничего хуже?

if(list.empty() == false){ // unprotected by mutex, okay if incorrect sometimes
    mutex.lock();
    if(list.empty() == false){ // check again while locked to be certain
         element = list.back();
         list.pop_back();
    }
    mutex.unlock();
}

Ответы [ 3 ]

10 голосов
/ 08 октября 2019

Это нормально, если звонок на list::empty() неправильный в 100% случаев.

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

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

6 голосов
/ 08 октября 2019

Существует чтение и запись (скорее всего, для size члена std::list, если мы предположим, что он назван так), которые не синхронизированы в отношении друг друга . Представьте, что один поток вызывает empty() (в вашем внешнем if()), в то время как другой поток входит во внутренний if() и выполняет pop_back(). Затем вы читаете переменную, которая, возможно, изменяется. Это неопределенное поведение.

2 голосов
/ 08 октября 2019

В качестве примера того, как все может пойти не так:

Достаточно умный компилятор может увидеть, что mutex.lock() не может изменить возвращаемое значение list.empty() и, таким образом, полностью пропустить внутреннюю проверку if, в конце концовприводя к pop_back в списке, последний элемент которого был удален после первого if.

Почему он может это сделать? Синхронизация в list.empty() отсутствует, поэтому, если бы она была изменена одновременно, это привело бы к гонке данных. Стандарт гласит, что программы не должны иметь гонки данных, поэтому компилятор примет это как должное (в противном случае он может почти не выполнять оптимизацию). Следовательно, он может предполагать однопоточную перспективу несинхронизированного list.empty() и делать вывод, что он должен оставаться постоянным.

Это только одна из нескольких оптимизаций (или поведения оборудования), которые могут нарушить ваш код.

...