C++ std::forward_list::在线程安全后插入_
在共享的C++ std::forward_list::在线程安全后插入_,c++,multithreading,thread-safety,c++14,forward-list,C++,Multithreading,Thread Safety,C++14,Forward List,在共享的std::forward_列表中,如果多个线程保证从不使用相同的位置迭代器调用insert_,那么并发调用after是否安全?考虑到insert保证不会使其他迭代器失效,并且容器没有size()方法,这似乎是安全的,但可能我遗漏了什么 编辑: 我编写了一个小的酷刑测试程序,在没有任何锁定的情况下运行良好: #include <forward_list> #include <iostream> #include <thread> #include <
std::forward_列表
中,如果多个线程保证从不使用相同的位置迭代器调用insert_,那么并发调用after
是否安全?考虑到insert保证不会使其他迭代器失效,并且容器没有size()
方法,这似乎是安全的,但可能我遗漏了什么
编辑:
我编写了一个小的酷刑测试程序,在没有任何锁定的情况下运行良好:
#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';
}
#包括
#包括
#包括
#包括
使用List=std::forward_List;
使用它=列表::常量迭代器;
void insertAndBranch(列表和列表、It、int-depth)
{
if(深度-->0){
It newIt=list.insert_在(It,depth)之后;
std::thread0([&]{insertAndBranch(list,it,depth);});
std::thread1([&]{insertAndBranch(list,newIt,depth);});
thread0.join();
thread1.join();
}
}
int main()
{
名单;
insertAndBranch(list,list.before_begin(),8);
std::vectorits;
for(It=list.begin();It!=list.end();+It){
把它推回(它);
}
std::vectorthreads;
因为(它:它的){
threads.emplace_back([&]{list.insert_在(it,-1);})之后);
}
用于(标准:螺纹和螺纹:螺纹){
thread.join();
}
用于(int i:列表){
标准::cout
在共享的std::list
中,多线程调用insert安全吗
同时,如果他们保证不会使用相同的
位置迭代器
不,那是不安全的…(不管怎样)
插入std::list
将需要访问上一个节点
和下一个节点
到迭代器位置,以及列表的大小
即使位置彼此相距很远,一个简单的事实是需要恒定时间(C++11)。这意味着每次插入都将更新std::list::size()
编辑:
在共享的std::forward_列表中
允许多个线程
如果保证从不调用insert_,则并发调用insert_
使用相同的位置迭代器
它不安全,不推荐使用。没有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
被插入
至于我为什么说“至少两个节点远离”,让我们把一个节点的情况拿走:假设两个线程分别想在C
之后插入K
,在D
之后插入M
:
Initial Forward_list
A -> B -> C -> D -> E
Insert K after C
A -> B -> C x D x E
\ / \ /
K M
发件人:
当表达式的计算写入内存位置时
另一个计算读取或修改相同的内存位置
据说表达方式是冲突的
当另一个线程将写入列表时,即使是读取操作也不是线程安全的(有关详细说明,请参阅);因此:多次写入不是线程安全的:
从没有锁的列表中读取时不会损坏列表,如果在另一个线程从列表中读取时修改列表,则任一线程都可能损坏(即崩溃或产生错误结果)
你需要某种锁。句型。< /P>复制的可能副本是旧的,而且事情已经改变。OP没有指定C++标准…C++ 14,我只是添加了一个标签。任何时候你有多个线程,其中一个或多个是共享变量的写入器,那么你需要同步。如果没有,你有一个未定义的数据竞争。行为。事实上,我在代码中使用的是
std::forward_list
,而不是std::list
。为了简单起见,我用std::list
写了这个问题,但我现在改了。有趣的是std::forward_list
没有大小方法,这会改变你的答案吗?在双链接列表中,但在单链列表中,你是正确的e链表唯一需要修改的两个节点是position参数指向的节点和新创建的节点,这两个节点都不会被另一个线程读取或修改。@atb,我已经更新了我的答案,以论证您的评论(请参见插图)。您仍然需要至少两个节点。尽管如此,这并不是一个保证。使用锁或其他具有线程安全列表的库在您的第一个图表中,我认为不会读取D。下一个节点指针将从C获取并提供给K。我无法想象为什么一个高效的实现需要从D读取。@atb,我刚刚通过对话关于这个问题的电子邮件。虽然“标准不能保证它,但他也想不到它会失败”…有了,我认为如果你能实现自己的保证,你就可以走了。你可能是对的,但我想知道为什么在这种情况下。这不是那么简单。例如,如果我们有一个带有一个元素的转发列表,那么:从一个线程读取其数据(a=l.begin()->d;
),然后在另一个线程中添加一个元素(l.insert_在(l.begin(),X)之后;
)不应触发任何竞争条件。这是因为两个线程中都没有访问共享数据:第一个线程只读取begin
和begin->d
,第二个线程写入begin->next
@ShmuelH.AFAIK除非调用const版本,否则不能保证begin
是线程安全的。begin()
可以做点什么(如果我