我知道标准库中的容器不是线程安全的。我以前认为类型为std::list
的容器不能同时被多个线程访问(其中一些可能会修改容器)。但现在似乎还有更多的东西比满足眼睛;更微妙的东西,不那么明显的东西,至少对我而言。
例如,考虑这个函数接受第一个参数按值:
void log(std::string msg, severity s, /*...*/)
{
return; //no code!
}
这是线程安全的吗?
首先,它似乎是线程安全的,因为函数体不访问共享的可修改的资源,因此是线程安全的。第二个想法是,在我调用这样一个函数时,会创建一个std::string
类型的对象,这是第一个参数,我认为这个对象的构造不是线程安全的,因为它内部使用std::allocator
,我认为这不是线程安全的。因此,调用这样的函数也不是线程安全的。但如果它是正确的,那么这个怎么样:
void f()
{
std::string msg = "message"; //is it thread-safe? it doesn't seem so!
}
我是对的吗?我们可以在多线程程序中使用std::string
(或在内部使用std::allocator
的任何容器)吗?
我特别将容器称为局部变量,而不是共享对象。
我搜索谷歌并发现了许多类似的疑问,没有具体的答案。我面临与他相似的问题:
请考虑C ++ 03和C ++ 11。
答案 0 :(得分:7)
在C ++ 11中,std::allocator
是线程安全的。从其定义:
20.6.9.1/6:备注:通过调用
获取存储空间::operator new(std::size_t)
以及::operator new
的定义:
18.6.1.4:
operator new
和operator delete
的库版本,全局operator new
和operator delete
的用户替换版本以及C标准库函数{{1} },calloc
,malloc
和realloc
必须 由于来自不同线程的并发调用,不会引入数据争用(1.10)。
C ++ 03没有线程概念,因此任何线程安全都是特定于实现的;您必须参考您的实施文档,看看它提供了什么保证,如果有的话。由于您正在使用Microsoft的实现,this page表示从多个线程写入同一类的多个容器对象是安全的,这意味着free
是线程安全的。
答案 1 :(得分:5)
在C ++ 11中,这将针对默认分配器解决:
20.6.9.1分配者成员[allocator.members]
除了析构函数,默认分配器的成员函数 由于并发调用,不得引入数据竞争(1.10) 来自不同线程的那些成员函数。打电话给这些 分配或取消分配特定存储单元的函数 应该在一个总订单中发生,并且每个这样的解除分配调用 应在下一次分配(如果有的话)之前按此顺序发生。
如果要在不同的线程中使用,任何用户提供的分配器都必须保持相同的约束。
当然,对于该标准的早期版本,由于他们没有谈论多线程,因此没有任何说法。如果一个实现是支持多线程(尽可能多或多数),它将负责处理这些问题。类似于实现为C和C ++提供线程安全的malloc()
(和其他库函数)的方式,即使最近之前的标准没有对此做出任何说明。
答案 2 :(得分:3)
正如您可能已经想到的那样,不会有一个简单的是或否答案。但是,我认为这可能有所帮助:
http://www.cs.huji.ac.il/~etsman/Docs/gcc-3.4-base/libstdc++/html/faq/index.html#5_6
我引用逐字:
5.6 libstdc ++ - v3线程安全吗?
libstdc ++ - v3在以下所有方面都力求成为线程安全的 条件得到满足:
系统的libc本身是线程安全的, gcc -v报告除'single'以外的线程模型, [仅限3.3之前]对于所讨论的体系结构,存在atomicity.h的非泛型实现。
答案 3 :(得分:2)
在调用std::string
期间复制log
时,分配器可能是线程安全的(在C ++ 11中是必需的),但是副本本身不是。因此,如果在发生复制时有另一个线程改变源字符串,则这不是线程安全的。
如果变异线程重新分配(例如通过附加新字符)或删除字符串,您可能会在变异之前和之前的另一半之后结束一半的字符串,或者甚至可能最终访问解除分配的内存,而副本仍在进行中。
OTOH,...
std::string msg = "message";
...如果您的分配器是线程安全的,则是线程安全的。