除非另有说明,否则C ++标准库对其类型提供以下保证:
(1)读操作(即在const
对象上工作)是线程安全的。这意味着只要没有线程正在同时向对象写入(应用非const
操作),多个线程可以同时从对象读取而没有竞争条件。
(2)多个线程可以同时读写任意对象,只要每个对象一次最多只能被一个线程访问。
标准库需要用户类型的相同保证。 (您可以在GotW #95中阅读有关此内容的内容,或观看Herb at C++ and Beyond 2012解释此内容。)
现在我的问题是,如果以下结论是正确的:由于operator()
的{{1}}是std::function
成员函数,因此它必须是线程安全的。如果在构造中传入的仿函数具有const const
成员函数,则operator()
对象可以假定它是线程安全的并且只是转发调用。但是,如果在构造中传递给它的仿函数有一个可变的std::function
,那么这个操作不需要是线程安全的,但operator()
仍然需要,因为调用操作符仍然是const。因此std::function
必须在外部同步对存储的可变仿函数的调用,因此使用互斥锁。这意味着在将可变lambda传递给std::function
的构造函数的情况下会产生性能开销。
这种推理是否正确?如果是,那么当前的实施是否合规?
答案 0 :(得分:5)
关于标准库对数据竞争安全性的保证,Herb所讨论的行为在C ++11§17.6.5.9中有详细说明:
17.6.5.9避免数据竞争[res.on.data.races]
1本节规定了实现为防止数据竞争而应满足的要求(1.10)。除非另有说明,否则每个标准库函数均应满足各项要求在下面指定的情况下,实现可能会阻止数据争用。
2 C ++标准库函数不应直接或间接访问除当前线程以外的线程可访问的对象(1.10),除非通过函数的参数直接或间接访问对象,包括
this
。3 C ++标准库函数不应直接或间接修改除当前线程以外的线程可访问的对象(1.10),除非通过函数的非const参数直接或间接访问对象,包括
this
。4 [注意:例如,这意味着实现不能在没有同步的情况下将静态对象用于内部目的,因为即使在未在线程之间显式共享对象的程序中,它也可能导致数据竞争。 - 后注]
5 C ++标准库函数不能通过其参数或其容器参数的元素访问可间接访问的对象,除非通过调用其规范在这些容器元素上所需的函数。
6通过调用标准库容器或字符串成员函数获得的迭代器操作可以访问底层容器,但不得修改它。 [注意:特别是,使迭代器无效的容器操作与与该容器关联的迭代器上的操作冲突。 - 后注]
7如果对象对用户不可见并且受到数据竞争保护,则实现可以在线程之间共享自己的内部对象。
8除非另有说明,否则C ++标准库函数应仅在当前线程内执行所有操作,如果这些操作具有对用户可见的效果(1.10)。
9 [注意:如果没有可见的副作用,这允许实现并行化操作。 - 后注]
假设您将lambda闭包传递给std::function
- 通过例如构造函数或赋值运算符 - 然后调用function
的{{1}}。在第1段中,允许operator()
“直接或间接通过函数的参数访问闭包对象,包括operator ()
。”在第2段中,它可能不会更改this
对象本身或闭包对象的状态,因为它们“直接或通过函数的非const参数间接访问,包括std::function
”。这种行为很容易实现,没有任何防止同时线程访问的保护,即锁定。
this
然后调用lambda闭包的operator ()
,规则发生变化:你的lambda的operator()
不是标准的库函数,因此不受规定行为的规则的约束。标准库函数。你可以根据语言规则对闭包对象做任何你喜欢的事。
标准库保证它不会通过其操作引入任何数据争用,但您负责代码可能引入的任何数据争用。