Есть ли способ получить неблокирующую вставку/извлечение потока в базовом 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.

Итак, ваши варианты:

  1. перепишите свой код, чтобы ваши потоки не получали доступ к объекту одновременно
  2. исправьте библиотеку boost и отправьте изменения обратно
  3. спросите Криса очень красиво, если он сделает изменения для платформы 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