2009-07-16 3 views
9

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

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

При переходе к окнам я решил использовать 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 компоненты были заперты отдельно, но это не так.

Есть ли альтернативная реализация операторов потока, которые я могу использовать? Могу ли я направить его не на блокировку? Должен ли я реализовать свои собственные (не уверен, как это сделать)?

Любые предложения будут оценены.

(Платформа 32- и 64-битными. Поведение наблюдается с Visual Studio 2003 Pro и 2008 Express)

+1

+1 Большой вопрос, хорошо сформулированы. Жаль, что у меня нет ответа для вас! –

ответ

1

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

Я уже определил, что проблема заключалась в том, что два потока зашли в тупик, пытаясь получить доступ к объекту iostream в отдельных операциях чтения и записи. Я мог видеть, что реализация Visual Studio для операторов вставки и извлечения строкового потока объявила Sentry, которая заблокировала буфер потока, связанный с потоком, который работает.

Я знал, что для рассматриваемого потока для этого тупика реализация буфера потока была повышена :: asio :: basic_socket_streambuf. Я проверил реализацию, чтобы увидеть, что операции чтения и записи (переполнение и переполнение) фактически работают с разными буферами (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 
      } 

Upside:

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

Даунсайд:

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

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

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

0

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

0

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

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

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

Я думаю, что его стек вызовов показывает проблему с блокировкой. –

+0

На самом деле я делаю поток потока на границах сообщений, но это не проблема. Другой конец этого соединения не застревает в ожидании данных. Скорее, два потока зашли в тупик на iostream 'sentry', которые определяют потоки Windows. Благодарим за внимание. –

1

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

Так что ваши варианты:

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

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

+0

Спасибо за ввод. Просто пояснить - этот вопрос относится конкретно к реализации Visual Studio шаблона basic_ostream, который расширяет класс блокировки и проверки ошибок, называемый Sentry. Это не проблема с библиотекой boost :: asio (хотя ваша рекомендация - хорошая вещь). –

+0

Вам нужно конвертировать из tcp :: iostream в std :: iostream в Visual Studio? Возможно, вам повезет больше, если вы сохраните его tcp :: iostream полностью. – teambob

+0

Не думал, что на всем пути ... Обычно я пишу свой класс в/из (чтение/запись и т. Д.) В терминах самого низкого общего знаменателя (istream/ostream). Спасибо за пищу для размышлений. –

0

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

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

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

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

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