Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Общие вопросы C/C++
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 23.12.2011, 13:21   #1
_Bers
Старожил
 
Регистрация: 16.12.2011
Сообщений: 2,329
По умолчанию Синхронизация потоков

Мне нужна сама идея. Алгоритм.

Вопрос: как заставить Основной поток ждать, пока данные не будут полностью прочитаны на том конце трубы?

Есть класс-обертка, предоставляющий удобный интерфейс работы с консолью.

Задача: перехват потока вывода stdout,

Допустим, есть такой код:

Код:
cout<< "1234567890"; //данные перехвачены, и скормлены console 
console <<"tttttttt";   //данные выводятся в консоль средствами WINAPI
Перехват stdout осуществляется с помощью безымянного пайпа.
Соответственно, данные от каутов улетают в трубу, где их читает дочерний поток.

Проблема в том, что даже при условии отсутствия буфера у самого sdtout, буфер пайпа устанавливает ОС Windows.

В итоге, основной поток нефига не ждет, пока данные от каута будут прочитаны, а сразу же приступает к исполнению следующей инструкции:

Код:
cout<< "1234567890"; //данные улетели в трубу. 
        //Может быть уже целиком, а может быть ещё даже ни одного байта
        // не долетело до другого конца.
        //Основной поток не ждет, когда на другом конце трубы данные 
        //будут прочитаны, а сразу переходит к выполнению следующей 
        //инструкции:

console <<"tttttttt"; //выполняет её.
В результате, вывод может получится таким:

"tttttttt1234567890".

То бишь, данные от каута пока долетели, консоль уже успела выполнить полностью вторую инструкцию.

Вопрос: как заставить Основной поток ждать, пока данные не будут полностью прочитаны на том конце трубы?

Я перепробовал кучу всяких способов. Использовал сигналы - все бесполезно. Максимум, чего я добился: это что бы работа основного потока приостанавливалась, пока дочерний читает, что бы не получилось так: "1234tttttttt567890".

Но вот заставить основной поток ждать, пока все данные от каута не будут прочитаны - не удалось.
_Bers вне форума Ответить с цитированием
Старый 23.12.2011, 13:42   #2
Mandrivnyk
Software Developer
Участник клуба
 
Аватар для Mandrivnyk
 
Регистрация: 01.03.2011
Сообщений: 1,098
По умолчанию

Как вариант -- ждать подтверждения получения полного пакета с той стороны.
Для этого имеет смысл "обернуть" исходящий текст служебными символами (STX -- в начале и ETX -- в конце) и ждать ответного сообщения (например, ACK) о том, что пакет данных полностью прочитан.
По-хорошему, надо еще добавлять вторым байтом длину сообщения, и предпоследним (перед ETX) -- контрольную сумму пакета.
Болтовня ничего не стоит. Покажите мне код. (c) Linus Torvalds
Помог ответ? -- Поставьте отзыв.
Выражения особой благодарности в рублевом эквиваленте отправлять сюда --> R269634919062
Mandrivnyk вне форума Ответить с цитированием
Старый 23.12.2011, 14:00   #3
_Bers
Старожил
 
Регистрация: 16.12.2011
Сообщений: 2,329
По умолчанию

Цитата:
Сообщение от Mandrivnyk Посмотреть сообщение
Как вариант -- ждать подтверждения получения полного пакета с той стороны.
Для этого имеет смысл "обернуть" исходящий текст служебными символами (STX -- в начале и ETX -- в конце) и ждать ответного сообщения (например, ACK) о том, что пакет данных полностью прочитан.
По-хорошему, надо еще добавлять вторым байтом длину сообщения, и предпоследним (перед ETX) -- контрольную сумму пакета.
Неееет. Закидывать в каут символ "завершения передачи" - не вариант.
Иначе теряется смысл самого перехвата. С таким же успехом, проще вообще не пользоваться каутом.

Мне нужно, что бы данные в каут отправлялись как обычно. При этом, работа основного потока приостанавливалась до тех пор, пока дочерний не сообщит о том, что он получил всю посылку.

Главная проблема заключается в том, что начало передачи самих данных контролирует ОС Windows и...

Я нигде не смог найти информацию, но создалось впечатление, что:

Получатель <---- труба <----буфер_трубы <----Отправитель


Данные от отправителя сначала залетают в буффер пайпа.
И уже потом, видимо штатными средствами ОС, данные из буфера отправляются в саму трубу. И уже только тогда, получатель читает очередную порцию.

Вот когда данные отправителя перетекли в буфер трубы, основной поток уже приступает к следующей инструкции.

А вот когда они из буфера трубы перетекут в саму трубу - это большой вопрос.

Может вообще получится так, что Отправитель успеет 100 разных инструкций выполнить, прежде чем Получатель прочитает хотя бы самый первый байт посылки...

Но это только моё предположение. Как оно на самом деле я не знаю...
Я то думал - данные от отправителя сразу в трубу попадут. Что труба - это и есть файл! В который можно писать, и читать.
_Bers вне форума Ответить с цитированием
Старый 23.12.2011, 14:53   #4
Mandrivnyk
Software Developer
Участник клуба
 
Аватар для Mandrivnyk
 
Регистрация: 01.03.2011
Сообщений: 1,098
По умолчанию

Цитата:
Мне нужно, что бы данные в каут отправлялись как обычно. При этом, работа основного потока приостанавливалась до тех пор, пока дочерний не сообщит о том, что он получил всю посылку.
Ну и откуда он будет знать, что посылка уже вся?
Болтовня ничего не стоит. Покажите мне код. (c) Linus Torvalds
Помог ответ? -- Поставьте отзыв.
Выражения особой благодарности в рублевом эквиваленте отправлять сюда --> R269634919062
Mandrivnyk вне форума Ответить с цитированием
Старый 23.12.2011, 15:12   #5
_Bers
Старожил
 
Регистрация: 16.12.2011
Сообщений: 2,329
По умолчанию

Цитата:
Сообщение от Mandrivnyk Посмотреть сообщение
Ну и откуда он будет знать, что посылка уже вся?
С этим я разобрался. Дочерний поток сигналит основному, что бы тот приостановился до тех пор, пока дочерний не уладит все свои дела.

После чего, дочерний поток начинает слушать трубу до тех пор, пока в ней не окажется пустота. Это будит означать, что он выкачал из неё все данные:

Код:
  while( 1 )
    {
        if( Number_Of_Bytes_InPipe()!=0 )      //чтение данных происходит только если в трубе что-то есть
        {
            ResetEvent(hReaded); //сигнал о том, что посылка получена потух
            Buff="";
            //StopGeneral();
            while(Number_Of_Bytes_InPipe()!=0)  //читаем порцию за порцией, пока не выкачаем всю посылку
            {
                ReadFile(hReadPipeOut, localBuff, MAXLENBUFF, &bytesRead, NULL);
                //Buff.assign(localBuff, localBuff+bytesRead); 
                Buff.append(localBuff, localBuff+bytesRead); 
            }
            TransportData(Buff);  //отправляем посылку конечному адресату
            
            SetEvent(hReaded); //сигнал о том, что посылка получена зажёгся
            //ContunueGeneral();
        }
        else
        {
            //если пришёл сигнал от основного потока, что пора завершать работу
            if( WaitForSingleObject(hCloseTRead,0)==WAIT_OBJECT_0 ) { break; }
        }
Проблема в том, как пофиксить сам факт начала передачи посылки?

Код:
cout << "1111"; //дочерний поток ещё даже не начал читать. 
                           //Поэтому, он никак не может просигналить основному, что бы тот приостановился.

console <<  "2222"; //Прежде, чем выполнить инструкцию, консоль сама должна каким то образом пофиксить,
                                 // что ей нужно ждать сигнала от дочернего. КАК ЕЙ ЭТО СДЕЛАТЬ?
Я нашёл один мерзкий-грязный способ, который работает. Он иллюстрирует суть проблемы, но решает её по-индусски.

Прежде, чем console начнет выполнять свою штатную работу, она производит проверку:

Код:
void WaitIntercept()
{
   Sleep(5); //подождём на всякий случай. Вдруг ОС ещё не успела закачать данные от каутов в трубу?
  
    if( Number_Of_Bytes_InPipe()!=0 ) //если за время ожидания, ОС закачала в трубу данные, значит перед инструкцией консоли сработал cout
    {
        WaitForSingleObject( GetSignal(), INFINITE); //ждем, пока дочерний поток не доделает свою работу
    }
}
Проблема в том, что если убрать время ожидания Sleep(5), то основной поток никак не может определить, должны ли в трубу вообще поступить данные, или нет.
И пока данные не поступили - дочерний поток тоже никак не сможет просигналить основному, что он начал/окончил чтение.

А вот когда данные от каутов поступят в трубу? Это большой вопрос...
Как определить, что сработал каут, и данные если прямо сейчас уже не поступили в трубу, то с секунды на секунду там появятся?

****

Код:
cout<< data; //данные уже улетели (куда-то), но в трубе пока ещё нет ни одного байта. 
                   //Куда улетели данные? И как пофиксить факт, что "куда-то данные от каутов пришли" ?
Я пробовал выяснить количество байт в трубе через хэндл записи, через хэндл чтения - бесполезно. Такое впечатление, что где бы данные не находились - к самому пайпу они не имеют отношения до тех пор, пока Windows не соизволит закачать их непосредственно в трубу...

Последний раз редактировалось _Bers; 23.12.2011 в 15:15.
_Bers вне форума Ответить с цитированием
Старый 23.12.2011, 22:57   #6
_Bers
Старожил
 
Регистрация: 16.12.2011
Сообщений: 2,329
По умолчанию

Вот такое решение имеется на данный момент:

//основной поток
1. Метод консоли первым делом скармливает cout особый символ-метку. После чего ожидает сигнал-разрешение на исполнение инструкции.
2. Получив разрешение (а он обязательное его получит. Либо почти сразу, либо чуть погодя), метод благополучно выполняет инструкцию.
3. После чего анлочит консоль. Дальше см пункт 1.


//дочерний поток
4. Данные от каута перехватываются Читателем, и направляются опять таки к консоли.
5. Там они поступают в приёмные покои, и ожидают когда консоль освободится. То есть, дочерний поток будит ждать до тех пор, пока консоль не выполнит свою текущую
инструкцию. И только потом сможет доставить посылку.

6. После того, как консоль освободилась, двери приёмных покоев раскрываются, и Читатель заходит внутрь...
7. Происходит анализ последнего символа очередной порции данных.
8. Если этот символ равен символу-метки, значит все, что было до этого символа - обычный каут. И данная метка свидетельствует, что "вся посылка получена"
9. В этом случае, консоль тут же лочится. Теперь Читатель уже с новой посылкой опять будит ждать.
10. А текущие данные отправляются на разделочный стол посылок.
11. После того, как посылка оприходована - Загорается фонарик "разрешение исполнить метод".
12. На этом Читатель завершает свой рабочий цикл, и начинает читать новую посылку.

//основной поток
13. А управление возвращается к методу, который запустил символ-метку в поток. Он дождался разрешения на исполнение инструкции. Исполняет её, и разлочивает консоль.
14. см пункт 6.

//Примечание.
15. Если пользователь не вызывал методы консоли - соответственно никто её и не лочил, соответственно, читатель со своими посылками сможет прибегать когда захочет.

Таким образом достигается синхронизация в обработке посылок от читателя, и методов самой консоли.
_Bers вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Синхронизация потоков kardinal94 Общие вопросы Delphi 5 29.11.2010 21:13
Синхронизация потоков alenka_ej Помощь студентам 0 03.06.2010 22:20
Синхронизация потоков в С++ erazer89 Помощь студентам 0 27.04.2010 20:14
синхронизация потоков m_kostik Win Api 0 26.03.2010 23:56