有没有办法在Windows中的basic_iostream获取非lockingstream的插入/提取?

我是一个C ++开发人员,直到最近我还不得不创build一个针对Windows的应用程序时,他主要在Solaris和Linux上进行编程。

我一直在使用基于TCP套接字支持的C ++ I / Ostream的通信devise。 这个devise是基于从stream中连续读取单个线程(大部分时间阻塞在套接字读取中等待数据),而其他线程通过同一个stream发送(通过互斥体同步)。

当移动到Windows时,我select使用boost :: asio :: ip :: tcp :: iostream来实现套接字stream。 我惊讶地发现上面的multithreadingdevise导致了Windows上的死锁。 看起来, operator<<(std::basic_ostream<...>,std::basic_string<...>)声明了一个“Sentry”,它locking整个stream的input和输出操作。 由于我读取的线程总是在stream中等待,所以当创build这个Sentry时,从其他线程发送死锁。

这里是运算符<<和Sentry构造过程中调用堆栈的相关部分:

  ... ntdll.dll!7c901046() CAF.exe!_Mtxlock(_RTL_CRITICAL_SECTION * _Mtx=0x00397ad0) Line 45 C CAF.exe!std::_Mutex::_Lock() Line 24 + 0xb bytes C++ CAF.exe!std::basic_streambuf<char,std::char_traits<char> >::_Lock() Line 174 C++ CAF.exe!std::basic_ostream<char,std::char_traits<char> >::_Sentry_base::_Sentry_base(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...}) Line 78 C++ CAF.exe!std::basic_ostream<char,std::char_traits<char> >::sentry::sentry(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...}) Line 95 + 0x4e bytes C++ > CAF.exe!std::operator<<<char,std::char_traits<char>,std::allocator<char> >(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...}, const std::basic_string<char,std::char_traits<char>,std::allocator<char> > & _Str="###") Line 549 + 0xc bytes C++ ... 

如果istream和ostream组件分开locking,我会好的,但情况并非如此。

是否有可以使用的stream操作符的替代实现? 我可以指示它不锁吗? 我应该实施自己的(不知道如何做到这一点)?

任何build议,将不胜感激。

(平台是Windows 32位和64位。在Visual Studio 2003 Pro和2008 Express中观察到的行为)

根据boost文档[1],使用两个线程访问没有互斥的对象是“不安全的”。 仅仅因为它在Unix平台上工作并不能保证它能在Windows平台上工作。

所以你的选择是:

  1. 重写你的代码,使你的线程不能同时访问这个对象
  2. 修补增强库并发回更改
  3. 问问Chris,他是否会为Windows平台做些改变

[1] http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/overview/core/threads.html

这个问题已经持续了很久。 即使有机会我会被嘲笑,我要报告我最终做了什么。

我已经确定问题是两个线程在尝试访问单独的读写操作中的iostream对象时发生死锁。 我可以看到Visual Studio实现的字符串流插入和提取操作符都声明了一个Sentry,它锁定了与正在操作的流相关的流缓冲区。

我知道,对于这个死锁问题的流,流缓冲区实现是boost :: asio :: basic_socket_streambuf。 我检查了实现,看到读写操作(下溢和溢出)实际上在不同的缓冲区上运行(get和put)。

通过上面的验证,我选择了简单地绕开这个应用程序的锁定。 为此,我使用项目特定的预处理器定义来排除锁定哨兵的basic_istream实现中的锁定代码:

  class _Sentry_base { // stores thread lock and reference to input stream public: __CLR_OR_THIS_CALL _Sentry_base(_Myt& _Istr) : _Myistr(_Istr) { // lock the stream buffer, if there #ifndef MY_PROJECT if (_Myistr.rdbuf() != 0) _Myistr.rdbuf()->_Lock(); #endif } __CLR_OR_THIS_CALL ~_Sentry_base() { // destroy after unlocking #ifndef MY_PROJECT if (_Myistr.rdbuf() != 0) _Myistr.rdbuf()->_Unlock(); #endif } 

潜在上升空间:

  • 有用
  • 只有我的项目(具有适当的定义)受到影响

下行:

  • 感觉有点哈克
  • 每个构建这个平台的平台都需要这个修改

我计划通过在代码和项目文档中大声记录这一点来缓解后一点。

我意识到可能有一个更优雅的解决方案,但为了方便起见,我在尽职调查后选择了一个直接的解决方案来了解影响。

也许你可以自己实现一个锁定层? IE,有一个单独的istreamostream ,当你被调用的时候你自己锁住了。 定期检查两者是否都解锁,然后从一个读入另一个。

写完之后,你是否明确地刷新了流? 这篇博文意味着你的数据可能会简单地被“卡住”在缓冲区中。 如果这是真的,那么也许你似乎陷入僵局,因为没有什么可读的。 添加stream << std::flush到您的发送操作结束。

博客文章建议的另一种(尽管效率较低)解决方案是关闭流的输出缓冲:

 stream.rdbuf()->pubsetbuf(0, 0); 

我知道这是一个古老的问题,但我必须自己做这个!

我的情况比较复杂,因为这是我自己的流氓,但是你可以通过这样做来解决这个问题:

 std::ostream &operator<<(std::ostream &x, std::string &y) { x.rdbuf()->_Unlock(); x << y.c_str(); } 

它被优先调用std :: version。

你当然必须为每一个Windows操作符做这个,它调用_Lock和(在我的情况下)所有你的streambuf中的读/写调用。