2017-02-17 16 views
2

В общем случае std::forward_list безопасно для нескольких потоков для вызова insert_after одновременно, если они не могут никогда называть его одинаковым итератором позиции? Кажется, что это может быть безопасным, учитывая, что вставка гарантируется, что не приведет к аннулированию других итераторов, а в контейнере нет метода size(), но, может быть, я что-то упустил?std :: forward_list :: insert_after безопасность потока

Edit:

Я написал тестовую программу небольшой пытки, которые, кажется, прекрасно работать в Clang без блокировки:

#include <forward_list> 
#include <iostream> 
#include <thread> 
#include <vector> 

using List = std::forward_list<int>; 
using It = List::const_iterator; 

void insertAndBranch (List& list, It it, int depth) 
{ 
    if (depth-- > 0) { 
     It newIt = list.insert_after (it, depth); 
     std::thread thread0 ([&]{ insertAndBranch (list, it, depth); }); 
     std::thread thread1 ([&]{ insertAndBranch (list, newIt, depth); }); 
     thread0.join(); 
     thread1.join(); 
    } 
} 

int main() 
{ 
    List list; 
    insertAndBranch (list, list.before_begin(), 8); 

    std::vector<It> its; 
    for (It it = list.begin(); it != list.end(); ++it) { 
     its.push_back (it); 
    } 
    std::vector<std::thread> threads; 
    for (It it : its) { 
     threads.emplace_back ([&]{ list.insert_after (it, -1); }); 
    } 
    for (std::thread& thread : threads) { 
     thread.join(); 
    } 

    for (int i : list) { 
     std::cout << i << ' '; 
    } 
    std::cout << '\n'; 
} 

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

+1

Возможный дубликат [std :: list threading push \ _back, front, pop \ _front] (http://stackoverflow.com/questions/1843567/stdlist-threading-push-back-front-pop-front) – peval27

+0

Дубликат старый, и все изменилось. –

+0

ну, OP не указывает стандарт C++ .. – peval27

ответ

1

В общем std::list это безопасно для нескольких потоков для вызова вставки одновременно, если они гарантированно никогда не называйте его с тем же позиции итератора?

Нет. Это было бы небезопасно ... (независимо от того, что).

Вставка в std::list потребуется доступ к previous node и next node в позиции итератора, а size списка.

Даже если позиции находятся далеко друг от друга, необходимо, чтобы std::list::size() был постоянным временем (C++ 11). Это означает, что каждая вставка обновит std::list::size().


Edit:

В общем std::forward_list это безопасно для нескольких потоков в insert_after вызовов одновременно, если они гарантированно никогда не называть его с той же позиции итератора?

Это небезопасно. и не рекомендуется. Контейнер STL не был спроектирован с безопасностью потока.


Во всяком случае, позволяет предположить некоторые основные гарантии, позволяет предположить очень простую версию std::forward_list: insert_after изменяет узел, на который указывает итератор так, что узел теперь указывает на вновь вставленной узел, в то время как вновь вставленной узел указывает на следующий узел. Таким образом, он будет только «безопасным», если итераторы будут расположены как минимум на два узла друг от друга, а ваш распределитель будет потокобезопасным.

Иллюстрация:

Initial Forward_list 
A -> B -> C -> D -> E 

Insert K after C 
A -> B -> C x D -> E 
      \/
      K 

Как вы можете видеть, C чтения и модификации. D может быть прочитан, а вставлен K.


Как почему я сказал, «по крайней мере, два узла прочь», давайте случай одного узла в сторону: Если предположить, что два потока вставить K после C и M после D соответственно:

Initial Forward_list 
A -> B -> C -> D -> E 

Insert K after C 
A -> B -> C x D x E 
      \/\/
      K M 

от cppreference:

При оценке выражения записывает в ячейку памяти и Другая оценка считывает или изменяет одно и то же местоположение памяти, говорят, что выражения конфликтуют.

+0

На самом деле я использую 'std :: forward_list', а не' std :: list' в моем коде. Я просто задал вопрос, используя 'std :: list', но я изменил его сейчас. Интересно, что 'std :: forward_list' не имеет метода измерения, так что это изменит ваш ответ? – atb

+0

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

+0

@atb, я обновил свой ответ, чтобы обсудить ваш комментарий (см. Иллюстрацию). Вы все равно должны быть как минимум на расстоянии в два узла. Тем не менее, это не гарантия. Используйте блокировки или какую-либо другую библиотеку с потоковым безопасным списком – WhiZTiM

2

даже Не читал операции потокобезопасны, когда другой поток пишет в список (см here для дальнейших объяснений); поэтому: несколько записей не являются потокобезопасными:

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

Вам нужен какой-то замок. Период.

+0

Возможно, вы правы, но мне было бы интересно узнать, почему в этом конкретном случае. – atb

+0

Это не так просто. Если, например, у нас есть прямой список с одним элементом, то: чтение его данных ('a = l.begin() -> d;') из одного потока и добавление элемента в другой ('l.insert_after (l. begin(), X); ') не должно вызывать никаких состояний гонки. Это связано с тем, что в обоих потоках нет доступа к общим данным: первый из них только читает 'begin' и' begin-> d', а второй записывает 'begin-> next'. –

+0

@ShmuelH. AFAIK не гарантирует, что 'begin' является потокобезопасным, если вы не вызываете версию const. 'begin()' мог бы что-то сделать (было бы безумным, если бы это произошло, но я думаю, что стандарт позволяет это), и теперь у вас есть гонка данных по вызовам 'begin'. – NathanOliver