boost::property_tree::ptree是线程安全的吗?

23

我在一段代码中使用boost的read_json函数,并在多个线程中调用。以下是这个调用的简化版本。其中一个线程(有时也是另一个线程)会导致segfault,这让我认为read_json不是线程安全的(或者我使用它的方式很愚蠢)。

void someclass::dojson() {
   using boost::property_tree::ptree;
   ptree pt;
   std::stringstream ss(json_data_string);

   read_json(ss,pt);
 }

现在json_data_string在这两个类之间是不同的(它只是通过套接字接收到的json数据)。

那么read_json是否线程安全,我需要对它进行互斥锁(最好不要),或者有更好的调用方式可以保证线程安全吗?

4个回答

22

由于boost json解析器依赖于boost::spirit,而spirit默认不支持多线程安全。

您可以在任何ptree头文件之前添加此宏来解决此问题。

#define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

2
如果你不在全局范围内定义它,而是在多个独立的cpp文件中使用Spirit/json_parser,它仍然会崩溃,因为令人讨厌的是,Sprit有一个静态变量将在所有文件之间共享。 - G Huxley

7

TL;DR:

我的建议:使用原子交换习语

ptree my_shared;
mutex shared_ptree_lock;

{
    ptree parsed;     // temporary
    read_json(ss,pt); // this may take a while (or even fail)

    lock_guard hold(shared_ptree_lock);
    std::swap(pt, my_shared); // swap under lock
}

现在,是否需要在读取之前锁定共享树,取决于您对线程上下文的了解(换句话说,取决于您是否知道同时可能正在修改您的树)。
为了使事情变得非常灵活,请通过shared_ptr<ptree>执行相同操作 - 但这将带来相当大的开销。问题是,使用交换惯用语时,您不必在读取方面锁定事物,因为读者将愉快地继续阅读旧树,并且如果他们完成阅读并释放shared_ptr,它最终会被销毁。
我不完全确定您期望什么。如果从两个线程进行写入,则无法使属性树具有线程安全性而不锁定。因此,我认为您的意思是,在同时在其他位置解析属性树时,属性树是否线程安全进行读取。
在这里,我的主要期望是:否。C ++有一种“按需付费”的文化,您不会看到任何线程安全的通用类。将有以下选项:
  • 预处理器#define切换线程安全
  • 政策模板参数管理行为
查看源代码后,令人惊讶的是,它看起来几乎是线程安全的。但不完全是:)
似乎没有#define或标志可以设置使属性树线程安全,因此您只能锁定。
原理:
查看internal_read_json后,我发现它仅访问流(应该私有地属于此阅读器,因为将流共享给多个(并发)用户几乎从不有用1),然后非常正确地仅使用解析器的上下文树与ptree的(pt)根节点进行交换。
显然,原子交换功能主要用于异常安全性(如果在解析JSON时出现异常,您不希望更改您的ptree)。但是,如果交换操作是线程安全的,则这也会使对pt的访问具有线程安全性。
遗憾的是,在ptree_implementation中,我们看到交换不是线程安全的:
template<class K, class D, class C> inline
void basic_ptree<K, D, C>::swap(basic_ptree<K, D, C> &rhs)
{
    m_data.swap(rhs.m_data);
    // Void pointers, no ADL necessary
    std::swap(m_children, rhs.m_children);
}

首先,你可能会在交换m_datam_children之间遇到竞态条件,此外,这些交换是标准的,不是原子交换。


1另外,显然istringstream不是线程安全的,因为它是C++98标准库中的一个类。


这也是我想的,所以我对此感到困惑。我将尝试简化代码,并查看结果。 - Ross W
11
好的,我进行了回溯并发现 read_json 正在使用 boost::spirit。我搜索了一下,发现如果使用 spirit,则应 #define BOOST_SPIRIT_THREADSAFE。我已经将其放到两个线程类的头文件中,并使用 -mt 库重新编译(我认为这对于 Linux 没有影响)。无论如何,它已经运行了 5 分钟而没有崩溃,这比以前要长得多。看看会怎样吧。 - Ross W
@RossW,干得好Ross。我甚至没有猜到它在使用Spirit,这很遗憾,因为我经常使用Spirit,本来可以帮你省去搜索的时间。赞一个,因为你找到了自己的答案。 - sehe
1
@Ross W:感谢您的指引。我也处于同样的情况(多个线程,完全独立的数据需要解析),并且在实例化Spirit语法时遇到了类似的崩溃问题。有了#define,现在一切都正常工作了。 - Ben
我也遇到了同样的问题,采用了相同的解决方案,谢谢。需要注意的是,对于我来说,除了添加定义之外,我没有做任何更改。随着批量POST测试的增加,我的服务器非常稳定,最终导致Firefox崩溃。赞boost asio! :-) - moodboom
显示剩余2条评论

1
在Boost 1.59中,ptree中的JSON解析器已被重写并发布。由于它不再使用Boost.Spirit,因此不再需要添加BOOST_SPIRIT_THREADSAFE定义到Property Tree中,并且read_json函数可以被认为是线程安全的。

1
请问您能否指向官方的Boost文档,以证明这一点。我已经查看了https://www.boost.org/users/history/version_1_59_0.html。在那里提到Property Tree使用了一个新的JSON解析器,但是您能否指出他们说它不使用Boost.Spirit的位置。 - Vivek Maran

0
感谢 Wei 和 liquidblueocean,#define 解决了我的问题。顺便提一下,当我有两个线程调用 read_json 时,我在 windbg !analyse -v 中获得了这个堆栈跟踪。
10 07b3e4fc 0021b2de sseng!_STL::for_each<_STL::reverse_iterator<boost::spirit::classic::impl::grammar_helper_base<boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > > * *>,_STL::binder2nd<_STL::mem_fun1_t<int,boost::spirit::classic::impl::grammar_helper_base<boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > >,boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > *> > >+0x11 [c:\ss\tp\aoo341\main\stlport\rel\inc\stlport\stl\_algo.h @ 65]
11 07b3e520 0021f867 sseng!boost::spirit::classic::impl::grammar_destruct<boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> > >+0x28 [c:\ss\tp\aoo341\main\boost\rel\inc\boost\spirit\home\classic\core\non_terminal\impl\grammar.ipp @ 325]
12 07b3e54c 002224fa sseng!boost::spirit::classic::grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> >::~grammar<boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >,boost::spirit::classic::parser_context<boost::spirit::classic::nil_t> >+0x1e [c:\ss\tp\aoo341\main\boost\rel\inc\boost\spirit\home\classic\core\non_terminal\grammar.hpp @ 52]
13 07b3e574 00226e37 sseng!boost::property_tree::json_parser::json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >::~json_grammar<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >+0x28
14 07b3e784 00226f5c sseng!boost::property_tree::json_parser::read_json_internal<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >+0x149 [c:\ss\tp\aoo341\main\boost\rel\inc\boost\property_tree\detail\json_parser_read.hpp @ 317]
15 07b3e7c0 00232261 sseng!boost::property_tree::json_parser::read_json<boost::property_tree::basic_ptree<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> >,_STL::less<_STL::basic_string<char,_STL::char_traits<char>,_STL::allocator<char> > > > >+0x25 [c:\ss\tp\aoo341\main\boost\rel\inc\boost\property_tree\json_parser.hpp @ 45]
16 07b3ea20 00232a28 sseng!SSPhone::Handshake+0x15b [c:\ss\xl\src\cpp\bin\eng\ssphone.cpp @ 272]
17 07b3ea5c 00234fc7 sseng!SSPhone::OnEvent+0x1a9 [c:\ss\xl\src\cpp\bin\eng\ssphone.cpp @ 232]
18 07b3fb7c 6f6b3433 sseng!PhoneThreadFunc+0x1ed [c:\ss\xl\src\cpp\bin\eng\ssthrd.cpp @ 198]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接