鉴于:
1)C ++ 03标准没有以任何方式解决线程的存在
2)C ++ 03标准让实现决定std::string
是否应该在其拷贝构造函数中使用Copy-on-Write语义
3)Copy-on-Write语义经常导致多线程程序中不可预测的行为
我得出以下看似有争议的结论:
您无法在多线程程序中安全且可移植地使用std :: string
显然,没有STL数据结构是线程安全的。但至少,使用std :: vector,您可以简单地使用互斥锁来保护对向量的访问。使用使用COW的std :: string实现,如果不在供应商实现中深入编辑引用计数语义,您甚至无法可靠地执行此操作。
真实世界的例子:
在我的公司,我们有一个多线程应用程序,经过彻底的单元测试,无数次通过Valgrind。该应用程序运行了几个月,没有任何问题。有一天,我在另一个版本的gcc上重新编译应用程序,突然之间我总是得到随机的段错误。 Valgrind现在在std :: string拷贝构造函数中报告libstdc ++深处的无效内存访问。
那么解决方案是什么?好吧,当然,我可以将std::vector<char>
作为字符串类进行输入 - 但实际上,这很糟糕。我还可以等待C ++ 0x,我祈祷将要求实现者放弃COW。或者,(颤抖),我可以使用自定义字符串类。我个人总是反对在预先存在的库可以正常运行时实现自己的类的开发人员,但老实说,我需要一个字符串类,我可以肯定它不使用COW语义;和std :: string根本不保证。
我是否正确std::string
在便携式多线程程序中根本无法可靠地使用?什么是好的解决方法?
答案 0 :(得分:11)
您无法安全,便携地在多线程程序中执行任何操作。没有可移植的多线程C ++程序,正是因为线程抛出了C ++关于操作顺序的所有内容,以及在窗口外修改任何变量的结果。
标准中也没有任何内容可以保证vector
可以按照您说的方式使用。提供带有线程扩展的C ++实现是合法的,例如,在初始化线程之外的任何使用向量会导致未定义的行为。你开始第二个线程的那一刻,你不再使用标准的C ++了,你必须向你的编译器供应商寻求安全和不安全的东西。
如果您的供应商提供了一个线程扩展,并且还提供了一个带有COW的std :: string,因此不能使其成为线程安全的,那么我认为您的论点暂时与您的供应商或线程有关。扩展,而不是C ++标准。例如,可以说POSIX应该在使用pthreads的程序中禁止COW字符串。
你可以通过使用一个互斥体来保证安全,你可以在做任何字符串变异时使用它,以及任何字符串读取都是复制的结果。但是你可能会对这个互斥锁进行严重的争论。
答案 1 :(得分:8)
你是对的。这将在C ++ 0x中修复。现在你必须依赖你的实现文档。例如,最近的libstdc ++版本(GCC)允许您使用字符串对象,就像一样 没有字符串对象与另一个共享缓冲区。 C ++ 0x强制库实现以保护用户免受“隐藏共享”。
答案 2 :(得分:4)
鉴于该标准没有对内存模型说一句话,并且完全不知道线程,我会说你不能肯定地认为每个实现都会非牛,所以没有,你不能
除此之外,如果你知道你的工具,大多数实现将使用非牛字符串来允许多线程。
答案 3 :(得分:4)
更正确的方法是“你无法在多线程环境中安全可移植地使用C ++”。无法保证其他数据结构也能表现得合理。或者运行时不会炸毁您的计算机。该标准不保证任何关于线程。
为了使用C ++中的线程执行任何,您必须依赖于实现定义的保证。然后您可以安全地使用std::string
,因为每个实现都会告诉您在线程环境中使用它是否安全。
在你产生第二个线程的那一刻,你失去了对真正可移植性的所有希望。 std::string
并不比语言/图书馆的其他部分“便携”。
答案 4 :(得分:2)
您可以使用STLport。它提供非COW字符串。它在不同平台上具有相同的行为。
这个article提供了STL字符串与写时复制和非复制的比较 - 写入算法,基于STLport字符串,绳索和GNU libstdc ++ 的实施方式。
在我工作的公司中,我有一些使用STLport构建的相同服务器应用程序的经验,而且在HP-UX 11.31上没有STLport。该应用程序使用gcc 4.3.1编译,优化级别为O2。因此,当我运行使用STLport构建的progrma时,与没有STLport(使用gcc自己的STL库)构建的相同程序相比,它处理请求的速度提高了25%。
我对这两个版本进行了分析,发现没有STLport的版本在pthread_mutex_unlock()
(2.5%)上花费的时间比使用STLport(1%)的版本多得多。并且在没有STLport的版本中pthread_mutex_unlock()
本身是从std :: string函数之一调用的。
但是,在分析之后,我以这种方式更改了大多数通常称为函数的字符串的赋值:
string_var = string_var.c_str(); // added .c_str()
没有STLport的版本的性能有了显着改善。
答案 5 :(得分:0)
我规范字符串访问:
std::string
成员设为私人const std::string&
这对我来说一直很好,并且是正确的数据隐藏。
答案 6 :(得分:0)
如果要禁用COW语义,可以强制字符串进行复制:
// instead of:
string newString = oldString;
// do this:
string newString = oldString.c_str();
正如所指出的,特别是如果你可以嵌入空值,那么你应该使用迭代器ctor:
string newString(oldString.begin(), oldString.end());
答案 7 :(得分:0)
在MSVC中,std :: string不再引用计数到容器的共享指针。他们在每个复制构造函数和赋值运算符中选择整个内容按值,以避免多线程问题。