Есть ли способ получить неблокирующую вставку/извлечение потока в базовом iostream в Windows?
Я разработчик C++, который в основном программировал на Solaris и Linux до недавнего времени, когда я был вынужден создать приложение, предназначенное для Windows.
Я использую дизайн связи на основе потока ввода-вывода c++, поддерживаемого сокетом TCP. Конструкция основана на одном потоке, непрерывно считываемом из потока (большую часть времени заблокированного в сокете, считываемом в ожидании данных), в то время как другие потоки отправляют через тот же поток (синхронизированный мьютексом).
когда перейдя к windows, я решил использовать boost::asio::ip::tcp:: iostream для реализации потока сокетов. Я был встревожен, обнаружив, что вышеупомянутый многопоточный дизайн привел к тупику в Windows. Похоже, что operator<<(std::basic_ostream<...>,std::basic_string<...>)
объявляет "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 были заблокированы отдельно, но это не так.
есть ли альтернативная реализация операторов потока, которые я могу использовать? Могу я приказать не запирать? Должен ли я реализовать свой собственный (не уверен, как это сделать)?
любые предложения будут оценены.
(Платформа Windows 32-и 64-разрядная. Поведение, наблюдаемое с помощью Visual Studio 2003 Pro и 2008 Express)
5 ответов
согласно документации boost [1] Использование двух потоков, обращающихся к одному объекту без мьютексов, является "небезопасным". Только потому, что он работал на платформах Unix, нет гарантии, что он будет работать на платформе Windows.
Итак, ваши варианты:
- перепишите свой код, чтобы ваши потоки не получали доступ к объекту одновременно
- исправьте библиотеку boost и отправьте изменения обратно
- спросите Криса очень красиво, если он сделает изменения для платформы 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. Я проверил реализацию, чтобы увидеть, что операции чтения и записи (underflow и overflow) фактически работают на разных буферах (get vs.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
}
плюсы:
- он работает
- затрагивается только мой проект (с соответствующими определениями)
недостаток:
- чувствует себя немного hacky
- каждой платформы, где это будет нужно модификация
Я планирую смягчить последний момент, громко документируя это в коде и проектной документации.
Я понимаю, что может быть более элегантное решение для этого, но в интересах целесообразности я выбрал прямое решение после должной осмотрительности, чтобы понять последствия.
возможно, вы могли бы реализовать блокировку слоя? И. Е., иметь отдельную istream
и ostream
которые вы сами блокируете, когда они вызываются. Периодически проверяйте, разблокированы ли оба, а затем считывайте из одного в другой.
вы явно смыли поток после записи в него? этот блог подразумевает, что ваши данные могут просто "застрял" в буфере. Если это правда, то, возможно, вы оказались в тупике, потому что пока нет ничего доступного для чтения. Добавить stream << std::flush
до конца ваших операций отправки.
альтернативным (хотя и менее эффективным) решением, предложенным в блоге, является отключение буферизации вывода потока:
stream.rdbuf()->pubsetbuf(0, 0);
Я знаю, это старый вопрос... но я должен был сделать это сам!
моя ситуация была сложнее, так как это был мой собственный streambuf, но вы можете исправить это, выполнив:
std::ostream &operator<<(std::ostream &x, std::string &y)
{
x.rdbuf()->_Unlock();
x << y.c_str();
}
который вызывается в предпочтении версии std::.
вы, конечно, должны сделать это для каждого оператора Windows