Доброго всем дня!
Есть проблема с потоками, может я что-то не так делаю или мысли не туда идут.
Есть задача: есть вектор данных ~3500 значений
В программе необходимо запустить обработку этих данных, приложение зависать не должно и пользователь должен видеть сколько записей обработано, сколько осталось.
Небольшой нюанс, есть записи которые обрабатываются продолжительное время.
моя реализация.
Создал семафор[10][10]
запустил || поток (дабы не зависло приложение)
в этом потоке запускаю кол-во потоков согласно семафору(10) в качестве параметра передаю значение из вектора
внутри запущенного потока обрабатываю переменные (сколько обработано, сколько осталось все через InterlockedExchangeAdd)
отображение заключил в EnterCriticalSection/LeaveCriticalSection
но приложение то нормально работает, то просто падает.
P.S. Мозг закипает.
Здравствуйте, SmileIlya, Вы писали:
SI>Создал семафор[10][10] SI>в этом потоке запускаю кол-во потоков согласно семафору(10) в качестве параметра передаю значение из вектора
Так 10 или 10*10 ?
По описанию проблемы сказать вряд ли что-то возможно, нужны более подробные сведения. Пока что не совсем понятно, зачем тут семафоры вообще.
Запускаем поток-менеджер. У него очередь для записей. Он запускает рабочие потоки (а лучше пул потоков) и передает им очередную запись. Они ставят его в известность об окончании через PostThreadMessage, в обработчике которой он принимает к сведению, что очередную запись обработали и запускает новый поток, передавая ему следующую запись (или дает новое задание пулу). Кстати, пулу можно и больше заданий дать, чем есть потоков, он сам разберется, правда, порядок при этом не определен, так и сейчас определен только порядок, в котором обработка начинается, но не порядок, в котором обработка записей заканчивается. Это вообще существенно — в каком порядке записи обрабатывать ?
Очередь синхронизировать не надо, так как с ней имеет дело только поток-менеджер. Для отображения та же PostThreadMessage, вызываемая или из рабочего, или из менеджера (чтобы не прыгать через голову начальника ) , только основному потоку (GUI), который только и будет обновлять вид.
По окончании своей работы поток-менеджер опять же через PostThreadMessage шлет сообщение GUI потоку о том, что он все сделал , и тихо заканчивается сам.
//поскипано SI> g_pDoc->m_pChild->SetStatusText( szTemp );
//поскипано
SI>P.S. Самое смешное, что все это нормально работало в 97 студии, при переходе на 2010 начались траблы
А вот это вот — выделенное — дяденька, нихарошо!!! Зачем же вы это из рабочего потока хулиганичичиете со строкой состояния в потоке пользовательского интерфейса? (ц)
Не стоит вообще говоря так делать. Имеет смысл вместо SetStatusText (ну ведь явно же вызов к строке состояния) напрямую дергать из рабочего потока. Отошлите лучше сообщение потоку пользовательского интерфейса...
Как-то так:
::SendMessage(hwndOfГлавноОкно_Или_чего_там_за_фрейм, WM_НАШЕ_СООБЩЕНИЕ,0, (LPARAM)(LPCTSTR)szTemp);
//Ну или как Павел выше написал
::PostThreadMessage(hwndOfГлавноОкно_Или_чего_там_за_фрейм, WM_НАШЕ_СООБЩЕНИЕ,0, (LPARAM)(LPCTSTR)szTemp);
Только нужно понимать, что в случае с PostThreadMessage сообщение будет обрабатываться асинхронно. Соответственно тогда нужно в LPARAM пихнуть указатель на строку в куче, ну а обработчик сообщений в главном потоке должен ее освободить потом. Ну и собственно уже обработчик сообщения в пользовательском потоке и дергает SetStatusText с указателем на строку.
Здравствуйте, SmileIlya, Вы писали:
SI>CChildFrame::SetStatusText(char* text) SI>{ SI> SetSendMessage(WM_SET_STATUS_TEXT, 0, (LPARAM)text); SI>}
SI>падает и с простым SetSendMessage(WM_SET_STATUS_TEXT, 0, (LPARAM)text); SI>уточню, падает на win7(x86) после обработки ~200-300 записей.
SI>P.S. Сорри за репост, сбойнул инет.
Нет, я не о том. Из рабочего потока нужно послать сообщение CChildFrame`у со строкой в LPARAM, а уж он пусть зовет SetStatusText. Проблема похоже в том, что пытаемся обратиться в CChildFrame`у из другого (рабочего) потока, нежели которому он принадлежит.
Здравствуйте, Carc, Вы писали:
C>Нет, я не о том. Из рабочего потока нужно послать сообщение CChildFrame`у со строкой в LPARAM, а уж он пусть зовет SetStatusText. Проблема похоже в том, что пытаемся обратиться в CChildFrame`у из другого (рабочего) потока, нежели которому он принадлежит.
Переделать на простую отправку сообщения CChildFrame не получится это один из базовых классов и менять его нет возможности, но разве критическая секция не подразумевает единоличное владение?
Здравствуйте, SmileIlya, Вы писали:
SI>Здравствуйте, Carc, Вы писали:
C>>Нет, я не о том. Из рабочего потока нужно послать сообщение CChildFrame`у со строкой в LPARAM, а уж он пусть зовет SetStatusText. Проблема похоже в том, что пытаемся обратиться в CChildFrame`у из другого (рабочего) потока, нежели которому он принадлежит.
SI>Переделать на простую отправку сообщения CChildFrame не получится это один из базовых классов и менять его нет возможности, Не надо переделывать CChildFrame, переделать надо рабочий поток
Вот тут
UINT CMyDoc::ThreadLoadProcSecondThread(LPVOID pParam)
{
CParamsForCalc* pParamsForCalc = (CParamsForCalc*)pParam;
/*
производится обработка данных (отправляется на сервак)
*/
::InterlockedExchangeAdd(&g_pDoc->m_lCountExec,(long)1);
char szTemp[ 256 ] = "\0";
sprintf(szTemp, "Обработано %ld осталось %ld", g_pDoc->m_lCountExec, g_pDoc->lCount);
g_pDoc->m_pChild->SetStatusText( szTemp );//заменить на ::SendMessage(HWND_of_m_pChild,...)
::InterlockedExchangeAdd(&g_pDoc->lCount,(long)-1);
g_pDoc->m_sema->Unlock();
return 0L;
}
Причем HWND лучше получать впрямую через параметры потоковой функции, а не как указатель на CChildFrame.
SI>но разве критическая секция не подразумевает единоличное владение?
Подразумевает. Но это тут вовсе не причем. Обьекты карты сообщений не любят когда с ними работают из другого потока. Поэтому и лучше использовать отправку сообщения.
C> g_pDoc->m_pChild->SetStatusText( szTemp );
C>//заменить на
C> ::SendMessage(HWND_of_m_pChild,...)
Вообще-то, MFC-шная отправка сообщения и голая WINAPI-шная мало чем отличаются.
Просто поменяем шило на мыло.
Но следует заметить, что синхронные сообщения между потоками — это достаточно страшная штука. Можно на ровном месте устроить дедлок.
Вот типичный антипаттерн в двух актах.
Акт первый. "Наивность".
Из рабочего потока посылаем WM_SETTEXT (ну или SetStatusText, который в итоге сведётся к WM_SETTEXT).
Оконный поток об этом не подозревает, параллельно тоже что-то там своё делает, — классическая гонка.
Особенно, если SetStatusText выполняет какую-то ещё полезную работу, кэширует строку, например.
Акт второй. "Тщета".
Оборачиваем всё, что связано со статусом, в критическую секцию.
Рабочий поток входит в секцию.
Рабочий поток отправляет сообщение — и, тем самым, ждёт, когда оконный поток это сообщение возьмёт в цикле прокачки и отработает.
Оконный поток подходит к критической секции и ждёт, когда рабочий поток её отпустит.
Ему бы в этом месте не тупо бездельничать, а прокачать очередь... но кто же знал?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Carc, Вы писали:
К>
C>> g_pDoc->m_pChild->SetStatusText( szTemp );
C>>//заменить на
C>> ::SendMessage(HWND_of_m_pChild,...)
К>
К>Вообще-то, MFC-шная отправка сообщения и голая WINAPI-шная мало чем отличаются. К>Просто поменяем шило на мыло.
К сожалению это действительно так. Падение не прекратилось.
Каким образом это переделать у меня закончилась фантазия, может будут какие-то предложения?
Здравствуйте, SmileIlya, Вы писали:
SI>Доброго всем дня! SI>Есть проблема с потоками, может я что-то не так делаю или мысли не туда идут. SI>Есть задача: есть вектор данных ~3500 значений SI>В программе необходимо запустить обработку этих данных, приложение зависать не должно и пользователь должен видеть сколько записей обработано, сколько осталось. SI>Небольшой нюанс, есть записи которые обрабатываются продолжительное время. SI>моя реализация. SI>Создал семафор[10][10] SI>запустил || поток (дабы не зависло приложение) SI>в этом потоке запускаю кол-во потоков согласно семафору(10) в качестве параметра передаю значение из вектора SI>внутри запущенного потока обрабатываю переменные (сколько обработано, сколько осталось все через InterlockedExchangeAdd) SI>отображение заключил в EnterCriticalSection/LeaveCriticalSection SI>но приложение то нормально работает, то просто падает. SI>P.S. Мозг закипает.
Здравствуйте, SmileIlya, Вы писали:
Сорри если пропустил и уже упомянули, но отличие SendMessage от PostMessage в том что первый — синхронный и блокирует основной поток обработки оконного класса (когда ты компилируешь приложение с флагом platform windows, тебе дается этот самый гуишный цикл обработки).
Вообще люди ж уже почти все разжевали здесь. ИМХО, (давать советы дело неблагодарное, но всеж рискну) тебе стоит немного отвлечься на другую задачу, так как создается впечатление что немного зациклился.
И почитать немного корифеев — лично мне в свое время здорово помогли статьи Joseph M. Newcomer. Вот эта, к примеру, по теме: http://www.flounder.com/workerthreads.htm
Здравствуйте, SmileIlya, Вы писали:
SI>К сожалению это действительно так. Падение не прекратилось. SI>Каким образом это переделать у меня закончилась фантазия, может будут какие-то предложения?
В данном случае между потоками строку не нужно таскать. Одно сообщение передаст в GUI-поток m_lCountExec (причем, всего один раз при старте ThreadLoadProcMainThread), второе — lCount (после каждого изменения). А строку форматировать и заталкивать в статус-бар или еще куда нужно в потоке GUI'я.
MT>В данном случае между потоками строку не нужно таскать. Одно сообщение передаст в GUI-поток m_lTotalCount (причем, всего один раз при старте ThreadLoadProcMainThread), второе — lProcessedCount (после каждого изменения). А строку форматировать и заталкивать в статус-бар или еще куда нужно в потоке GUI'я.
fixed.
Здравствуйте, MTimur, Вы писали:
MT>>В данном случае между потоками строку не нужно таскать. Одно сообщение передаст в GUI-поток m_lTotalCount (причем, всего один раз при старте ThreadLoadProcMainThread), второе — lProcessedCount (после каждого изменения). А строку форматировать и заталкивать в статус-бар или еще куда нужно в потоке GUI'я. MT>fixed.
Посмотрел повнимательнее код. Собственно даже эти данные передавать не нужно, они и так в CMyDoc находятся. Достаточно тупо периодически обновлять текст из основного потока (по таймеру или в ответ на "пустое" сообщение от рабочего потока)
Здравствуйте, SmileIlya, Вы писали:
SI>Каким образом это переделать у меня закончилась фантазия, может будут какие-то предложения?
Два способа.
Первый — сделать рабочий поток полностью ведомым объектом.
То есть, пусть у нас есть данные, которые поток перемалывает, и к этим данным несколько функций доступа:
— из процедуры потока, — что-то там молоть
— из откуда угодно, — получить текущий статус (можно в виде строки)
Естественно, публичные данные защищены критической секцией. Важно, что время пребывания в критической секции должно быть детерминированным (никаких ожиданий, никаких синхронных вызовов наподобие того же SendMessage) и коротким. Т.е. рабочий поток не должен зайти в секцию и устроить там числодробилку на несколько секунд: нет, он зашёл, нахватался нужных данных, вышел, и грызёт их сколько влезет; потом зашёл, покидал результаты, вышел.
Процедура потока с какой-то периодичностью посылает совершенно асинхронное сообщение в оконный поток (можно — к конкретному окну): "у меня обновился статус, посмотри".
Окно, когда захочет (может — вообще по таймеру, а не по намёкам от рабочего) лезет в публичные данные, возможно, на несколько микросекунд спотыкается об критическую секцию, берёт, выходит и показывает в окошках.
Второй способ — сделать ведомым оконный поток.
Это более запарно, поскольку у него штатная синхронизация — это очередь оконных сообщений.
Использовать критические секции нельзя, я уже объяснил выше почему. Можно использовать синхрообъекты ядра (тяжёлые мьютексы, семафоры и т.п.), но их ожидание следует делать с помощью MsgWaitForMultipleObjects.
Здравствуйте, SmileIlya, Вы писали:
C>>> g_pDoc->m_pChild->SetStatusText( szTemp ); C>>>//заменить на C>>> ::SendMessage(HWND_of_m_pChild,...)
SI>К сожалению это действительно так. Падение не прекратилось. SI>Каким образом это переделать у меня закончилась фантазия, может будут какие-то предложения?
А в каком месте падает?
Код Ваш смахивает на творение новичка в программировании, посему предположу, что Вы могли передать указатель на локальную переменную szTemp. Не Ваш вариант?
В качестве оффтопа еще рекомендую для таких задач использовать какую нибудь готовую реализацию пула потоков. Он создаст и оптимальное количество потоков( к примеру — кратное количеству ядер ) и не будет постоянно создавать и завершать потоки в целях оптимизации, как в Вашем случае.
Здравствуйте, SmileIlya, Вы писали:
SI>P.S. Мозг закипает.
Сделай пул потоков в общей очередью.
Поток, который закончил обрабатывать данные, должен вызвать PostMessage() и отправить сообщение окну, которое отображает данные. Затем поток должен взять из очереди новые данные для обработки. Если данных нет — закрыть семафор для этого потока и ждать, пока он освободится.
После добавления данных в очередь — открыть семафор для потоков, которые ничего не делают.
Для работы с очередью — создать мьютекс. Для приостановки работы потоков, которые ничего не делают — по одному семафору на поток.