Есть ли способ получить неблокирующую вставку / извлечение потока в basic_iostream в Windows? - PullRequest
9 голосов
/ 17 июля 2009

Я разработчик на C ++, который до недавнего времени программировал преимущественно на Solaris и Linux, когда мне пришлось создавать приложение, ориентированное на Windows.

Я использовал коммуникационный дизайн, основанный на потоке ввода-вывода C ++, поддерживаемом сокетом TCP. Конструкция основана на том, что один поток постоянно читает данные из потока (большую часть времени блокирует чтение в сокете в ожидании данных), в то время как другие потоки отправляют через тот же поток (синхронизированный с мьютексом).

При переходе на окна я решил использовать boost :: asio :: ip :: tcp :: iostream для реализации потока сокетов. Я был встревожен, обнаружив, что вышеуказанный многопоточный дизайн привел к тупику в Windows. Похоже, что operator<<(std::basic_ostream<...>,std::basic_string<...>) объявляет 'Sentry', который блокирует весь поток для операций ввода и вывода. Поскольку мой поток чтения всегда ожидает в потоке, операции отправки из других потоков блокируются при создании этого Sentry.

Вот соответствующая часть стека вызовов во время оператора << и построения Sentry: </p>

    ...
    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 ]

1 голос
/ 31 июля 2009

Этот вопрос достаточно долго томился. Я собираюсь сообщить о том, что я в итоге сделал, хотя есть шанс, что меня высмеют.

Я уже определил, что проблема в том, что два потока зашли в тупик при попытке доступа к объекту 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
            }

Вверх:

  • Работает
  • Затрагивается только мой проект (с соответствующими определениями)

Даунсайд:

  • Чувствую себя немного хакером
  • Каждая платформа, на которой он построен, нуждается в этой модификации

Я планирую смягчить последний момент, громко документировав это в коде и документации проекта.

Я понимаю, что для этого может быть более изящное решение, но в интересах целесообразности я выбрал прямое решение после должной осмотрительности, чтобы понять последствия.

1 голос
/ 24 июля 2009

Согласно документации надстройки [1] использование двух потоков, обращающихся к одному объекту без мьютексов, «небезопасно». Тот факт, что он работал на платформах Unix, не гарантирует, что он будет работать на платформе Windows.

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

  1. Перепишите ваш код, чтобы ваши потоки не обращались к объекту одновременно
  2. Исправьте библиотеку надстройки и отправьте изменения обратно
  3. Спросите Криса очень мило, внесет ли он изменения для платформы Windows

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

0 голосов
/ 19 декабря 2012

Я знаю, что это старый вопрос ... но я просто должен был сделать это сам!

Моя ситуация была более сложной, так как это был мой собственный потоковый буфер, но это можно исправить, выполнив:

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

, который вызывается в предпочтении std :: version.

Конечно, вы должны будете сделать это для каждого оператора Windows <<, который вызывает _Lock и (в моем случае) все ваши вызовы чтения / записи в вашем потоке bub. </p>

0 голосов
/ 23 июля 2009

Вы явно сбросили поток после записи в него? Это сообщение в блоге подразумевает, что ваши данные могут просто "зависнуть" в буфере. Если это правда, то, возможно, вы зашли в тупик, потому что для чтения еще ничего нет. Добавьте stream << std::flush в конец ваших операций отправки.

Альтернативное (хотя и менее эффективное) решение, предлагаемое постом в блоге, - отключить буферизацию потока потока:

stream.rdbuf()->pubsetbuf(0, 0);
0 голосов
/ 17 июля 2009

Возможно, вы могли бы реализовать слой блокировки самостоятельно? То есть, у вас есть отдельные istream и ostream, которые вы сами блокируете, когда они вызываются. Периодически проверяйте, разблокированы ли оба, и затем читайте одно в другое.

...