LINUX.ORG.RU

std::deque data races

 ,


0

2

Добрый день. Можно ли делать так без синхронизации:

std::deque<T> d;
// thread 1
while(true){
    d.push_back(...);
}

// thread 2
while(true){
    for(size_t i = d.size() - 1;  i >= 0;  -- i){
        T &e = d[];
        ...
        // используем e
    }
}

Насколько я знаю, нельзя. Стандарт никак не регламентирует потокобезопасность контейнеров. И в обычных реализациях её нет.

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)

Мне почему-то кажется, что тут будет integer overflow. 0 - 1 == __SIZE_MAX__. Надо подождать местных гуру.

Deleted ()

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

DarkEld3r ★★★★★ ()

Нет.

Основное общее правило: если из разных потоков хочется работать(читать и/или писать) с общими данными, то, во-первых, объявляешь их volatile, а, во-вторых, делаешь mutex.lock()/unlock().

Другие варианты - это частности, в зависимости от требуемой оптимизации, архитектуры и компилятора.

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

Стандарт никак не регламентирует потокобезопасность контейнеров.

Регламентирует, как раз то, что просто читать из разных потоков безопасно.

asaw ★★★★★ ()

Я ведь почему выбрал deque и индексы? 1. Вставка не трогает существующие элементы 2. Не используются итераторы, которые невалидны после вставки. Аргументов за пока не нашёл. Про volatile и 0 - 1 == __SIZE_MAX__ согласен.

pavlick ★★ ()
Ответ на: комментарий от pavlick

Вставка не трогает существующие элементы

Вставка трогает индекс чанков который обычно в deque есть какое-то подобие дерева, всё равно получается состояние гонки и UB. Не трогать существующие элементы это сохранение валидными указателей на сами элементы.

mashina ★★★★★ ()
Ответ на: комментарий от pavlick

Про volatile ... согласен.

Про volatile написана чушь, оно в данном случе не нужно и помочь ничем не может.

mashina ★★★★★ ()

Смежный вопрос: как «переварить» memore_order http://en.cppreference.com/w/cpp/atomic/memory_order? Пару раз пытался осилить по этой ссылке, тщетно. Можете что-нибудь порекомендовать (литература), из личного опыта? Не то чтобы совсем не понимаю о чём речь, но не доволен знанием вопроса.

pavlick ★★ ()
Ответ на: комментарий от pavlick

За ссылку спасибо, кстати — интересная весчь.

А что непонятно? Вроде дословный перевод достаточно понятен, чтобы разобраться о чем речь. Если есть какие-то конкретные вопросы, возможно стоит отдельный тред создать.

Kiborg ★★★ ()
Ответ на: комментарий от asaw

Регламентирует, как раз то, что просто читать из разных потоков безопасно.

Но автор темы не только читает. Он ещё и пишет из соседнего потока.

Deleted ()
for(size_t i = d.size() - 1;  i >= 0;  -- i){

У вас уже на стадии компиляции должно быть предупреждение. В данном случае вы объявили беззнаковое целое, которое декрементится и и сравнивается >= 0. А беззнаковое целое всегда >= 0. Цикл бесконечен.

Тут стоит переписать так:

for(size_t i = 0, size = d.size(); i < 0; i++)
{
   const size_t idx = size - i - 1;
   ...
andreyu ★★★★★ ()
Ответ на: комментарий от andreyu

Ну не пинайте сильно, это ведь псевдокод для описания сути. В реальности (при декременте) сравниваю счётчик с std::limits<type>::max(). Выше я уже признал ошибку в коде.

pavlick ★★ ()
Ответ на: комментарий от asaw

Просто читать можно, читать и писать - нет.

Пруфец бы на то что pop из дека можно делать из разных потоков. Скорее всего там будет гонка во все поля.

anonymous ()
Ответ на: комментарий от anonymous

В каком? В двух последних про это довольно много.

anonymous ()
Ответ на: комментарий от anonymous

pop - это не чтение. top можешь делать сколько хочешь.

anonymous ()
Ответ на: комментарий от DarkEld3r

Плохое влияние с++2003(с++98). В с++11 явно убрали с volatile семантику, относящуюся к многопоточности, поэтому - незачем.

Я специально подчеркнул, что правило максимально общее для C++, без указания стандарта и платформы. И тем более, это практическое мое имхо. Пишите в рамках C++11(и выше) - меньше будет таких заморочек.

http://www.drdobbs.com/cpp/volatile-the-multithreaded-programmers-b/184403766 - Вот Александреску в старой статье рассказывает.

Deleted ()
Ответ на: комментарий от Deleted

В с++11 явно убрали с volatile семантику, относящуюся к многопоточности

Хочешь сказать, что в старом стандарте можно было обьявить контейнер целиком volatile и всё бы заработало? Ну фигня же.

DarkEld3r ★★★★★ ()
Ответ на: комментарий от DarkEld3r

нет. В с++2003, формально, нужно было делать и volatile и mutex. В с++11 - только mutex, без всяких формальностей. Раньше работало без volatile - потому, что таковы самые распространенные реализации. Мютекс лок/анлок для доступа на чтение и на запись - в общем случае нужен всегда, это даже не обсуждается.

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

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от asaw

pop нельзя - это изменяющая операция. Можно только const.

Внезапно правда? Тогда какой толк от такой потокобезопасности? То есть задача например такая: есть писатели, и несколько читателей. И? Тыква! Без синхронизации читателям возьмут одинаковые элементы.

anonymous ()
Ответ на: комментарий от anonymous

Тогда какой толк от такой потокобезопасности?

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

asaw ★★★★★ ()
Последнее исправление: asaw (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.