Проблема с потоками
От: SmileIlya  
Дата: 12.07.14 15:05
Оценка: -1
Доброго всем дня!
Есть проблема с потоками, может я что-то не так делаю или мысли не туда идут.
Есть задача: есть вектор данных ~3500 значений
В программе необходимо запустить обработку этих данных, приложение зависать не должно и пользователь должен видеть сколько записей обработано, сколько осталось.
Небольшой нюанс, есть записи которые обрабатываются продолжительное время.
моя реализация.
Создал семафор[10][10]
запустил || поток (дабы не зависло приложение)
в этом потоке запускаю кол-во потоков согласно семафору(10) в качестве параметра передаю значение из вектора
внутри запущенного потока обрабатываю переменные (сколько обработано, сколько осталось все через InterlockedExchangeAdd)
отображение заключил в EnterCriticalSection/LeaveCriticalSection
но приложение то нормально работает, то просто падает.
P.S. Мозг закипает.
Re: Проблема с потоками
От: Pavel Dvorkin Россия  
Дата: 13.07.14 02:34
Оценка: +2
Здравствуйте, SmileIlya, Вы писали:

SI>Создал семафор[10][10]

SI>в этом потоке запускаю кол-во потоков согласно семафору(10) в качестве параметра передаю значение из вектора

Так 10 или 10*10 ?

По описанию проблемы сказать вряд ли что-то возможно, нужны более подробные сведения. Пока что не совсем понятно, зачем тут семафоры вообще.

Запускаем поток-менеджер. У него очередь для записей. Он запускает рабочие потоки (а лучше пул потоков) и передает им очередную запись. Они ставят его в известность об окончании через PostThreadMessage, в обработчике которой он принимает к сведению, что очередную запись обработали и запускает новый поток, передавая ему следующую запись (или дает новое задание пулу). Кстати, пулу можно и больше заданий дать, чем есть потоков, он сам разберется, правда, порядок при этом не определен, так и сейчас определен только порядок, в котором обработка начинается, но не порядок, в котором обработка записей заканчивается. Это вообще существенно — в каком порядке записи обрабатывать ?

Очередь синхронизировать не надо, так как с ней имеет дело только поток-менеджер. Для отображения та же PostThreadMessage, вызываемая или из рабочего, или из менеджера (чтобы не прыгать через голову начальника ) , только основному потоку (GUI), который только и будет обновлять вид.

По окончании своей работы поток-менеджер опять же через PostThreadMessage шлет сообщение GUI потоку о том, что он все сделал , и тихо заканчивается сам.
With best regards
Pavel Dvorkin
Re[2]: Проблема с потоками
От: SmileIlya  
Дата: 13.07.14 06:12
Оценка: :)
Может я что-то не так описал, для полного понимания вот код:



// запускается из View
CWinThread* thread = AfxBeginThread( 
     CMyDoc::ThreadLoadProcMainThread, 
     (LPVOID)GetDocument());
// Document

UINT CMyDoc::ThreadLoadProcMainThread(LPVOID pParam)
{
    CMyDoc* doc = (CMyDoc*)pParam;
    doc->lCount = doc->m_vData.size();
    g_pDoc = doc; // определен глобально
    if(!doc->m_vData.empty())
    {    
        std::vector<CParamsForCalc*>::iterator it = doc->m_vData.begin();
        for(; it != doc->m_vData.end() ; ++it)
        {
            doc->m_sema->Lock();
            CWinThread* thread = AfxBeginThread(
            CCrPercentDoc::ThreadLoadProcSecondThread, 
            (LPVOID)(*it));
        }
    }
    return 0L;
}


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 );
    ::InterlockedExchangeAdd(&g_pDoc->lCount,(long)-1);
    g_pDoc->m_sema->Unlock();
    
    return 0L;
}


в конструкторе документа

m_sema = new CSemaphore(10,10);
::InitializeCriticalSection(&g_cs);


P.S. Самое смешное, что все это нормально работало в 97 студии, при переходе на 2010 начались траблы
Re[3]: Проблема с потоками
От: Carc Россия https://vk.com/gosha_mazov
Дата: 13.07.14 06:55
Оценка: +1
//поскипано
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 с указателем на строку.
Aml Pages Home
Re[4]: Проблема с потоками
От: SmileIlya  
Дата: 13.07.14 08:40
Оценка:
Здравствуйте, Carc, Вы писали:

C>//поскипано

SI>> g_pDoc->m_pChild->SetStatusText( szTemp );
C>//поскипано

CChildFrame::SetStatusText(char* text)
{
SetSendMessage(WM_SET_STATUS_TEXT, 0, (LPARAM)text);
}

падает и с простым SetSendMessage(WM_SET_STATUS_TEXT, 0, (LPARAM)text);
уточню, падает на win7(x86) после обработки ~200-300 записей.

P.S. Сорри за репост, сбойнул инет.
Re[5]: Проблема с потоками
От: Carc Россия https://vk.com/gosha_mazov
Дата: 13.07.14 09:03
Оценка:
Здравствуйте, 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`у из другого (рабочего) потока, нежели которому он принадлежит.
Aml Pages Home
Re[6]: Проблема с потоками
От: SmileIlya  
Дата: 13.07.14 10:08
Оценка:
Здравствуйте, Carc, Вы писали:

C>Нет, я не о том. Из рабочего потока нужно послать сообщение CChildFrame`у со строкой в LPARAM, а уж он пусть зовет SetStatusText. Проблема похоже в том, что пытаемся обратиться в CChildFrame`у из другого (рабочего) потока, нежели которому он принадлежит.


Переделать на простую отправку сообщения CChildFrame не получится это один из базовых классов и менять его нет возможности, но разве критическая секция не подразумевает единоличное владение?
Re: Проблема с потоками
От: rm822 Россия  
Дата: 13.07.14 10:22
Оценка:
Не изобретайте велосипеды


#include "stdafx.h"
#include <stdio.h>
#include <vector>
#include <windows.h>

class MyClass
{
public:
    void DoJob() { Sleep(__rdtsc() % 1000); }
};

void OnProgress(double progress)
{
    printf("%i\n", (int)(progress * 100));
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::vector<MyClass> v;
    for (size_t i = 0; i < 100; ++i)
        v.push_back(MyClass());

    size_t iteration = 0;
    const DWORD mainThread = GetCurrentThreadId();
    #pragma omp parallel for shared(v, iteration) schedule(dynamic, 1) 
    for (ptrdiff_t i = 0; i < static_cast<ptrdiff_t>(v.size()); ++i)
    {
        v[i].DoJob();
        #pragma omp atomic
        iteration += 1;
        
        if (GetCurrentThreadId() == mainThread)
            OnProgress(iteration*1.0 / v.size());
    }
    return 0;
}
Re[7]: Проблема с потоками
От: Carc Россия https://vk.com/gosha_mazov
Дата: 13.07.14 10:26
Оценка:
Здравствуйте, 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>но разве критическая секция не подразумевает единоличное владение?

Подразумевает. Но это тут вовсе не причем. Обьекты карты сообщений не любят когда с ними работают из другого потока. Поэтому и лучше использовать отправку сообщения.
Aml Pages Home
Re[8]: Проблема с потоками
От: Кодт Россия  
Дата: 13.07.14 23:39
Оценка:
Здравствуйте, Carc, Вы писали:

C>    g_pDoc->m_pChild->SetStatusText( szTemp );
C>//заменить на 
C>    ::SendMessage(HWND_of_m_pChild,...)


Вообще-то, MFC-шная отправка сообщения и голая WINAPI-шная мало чем отличаются.
Просто поменяем шило на мыло.

Но следует заметить, что синхронные сообщения между потоками — это достаточно страшная штука. Можно на ровном месте устроить дедлок.
Вот типичный антипаттерн в двух актах.

Акт первый. "Наивность".
Из рабочего потока посылаем WM_SETTEXT (ну или SetStatusText, который в итоге сведётся к WM_SETTEXT).
Оконный поток об этом не подозревает, параллельно тоже что-то там своё делает, — классическая гонка.
Особенно, если SetStatusText выполняет какую-то ещё полезную работу, кэширует строку, например.

Эврика! Критические секции! (через фьютексы, мьютексы, семафоры, рукодельщину — неважно).

Акт второй. "Тщета".
Оборачиваем всё, что связано со статусом, в критическую секцию.

Рабочий поток входит в секцию.
Рабочий поток отправляет сообщение — и, тем самым, ждёт, когда оконный поток это сообщение возьмёт в цикле прокачки и отработает.
Оконный поток подходит к критической секции и ждёт, когда рабочий поток её отпустит.
Ему бы в этом месте не тупо бездельничать, а прокачать очередь... но кто же знал?

Акт третий. "Искупление". Напишу позже.
Перекуём баги на фичи!
Re[9]: Проблема с потоками
От: SmileIlya  
Дата: 14.07.14 07:00
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, Carc, Вы писали:


К>
C>>    g_pDoc->m_pChild->SetStatusText( szTemp );
C>>//заменить на 
C>>    ::SendMessage(HWND_of_m_pChild,...)
К>


К>Вообще-то, MFC-шная отправка сообщения и голая WINAPI-шная мало чем отличаются.

К>Просто поменяем шило на мыло.

К сожалению это действительно так. Падение не прекратилось.
Каким образом это переделать у меня закончилась фантазия, может будут какие-то предложения?
Re: Проблема с потоками
От: saf_e  
Дата: 14.07.14 08:34
Оценка: +1
Здравствуйте, SmileIlya, Вы писали:

SI>Доброго всем дня!

SI>Есть проблема с потоками, может я что-то не так делаю или мысли не туда идут.
SI>Есть задача: есть вектор данных ~3500 значений
SI>В программе необходимо запустить обработку этих данных, приложение зависать не должно и пользователь должен видеть сколько записей обработано, сколько осталось.
SI>Небольшой нюанс, есть записи которые обрабатываются продолжительное время.
SI>моя реализация.
SI>Создал семафор[10][10]
SI>запустил || поток (дабы не зависло приложение)
SI>в этом потоке запускаю кол-во потоков согласно семафору(10) в качестве параметра передаю значение из вектора
SI>внутри запущенного потока обрабатываю переменные (сколько обработано, сколько осталось все через InterlockedExchangeAdd)
SI>отображение заключил в EnterCriticalSection/LeaveCriticalSection
SI>но приложение то нормально работает, то просто падает.
SI>P.S. Мозг закипает.

// запускается из View
CWinThread* thread = AfxBeginThread( 
     CMyDoc::ThreadLoadProcMainThread, 
     (LPVOID)GetDocument());
// Document

UINT CMyDoc::ThreadLoadProcMainThread(LPVOID pParam)
{
    CMyDoc* doc = (CMyDoc*)pParam;
    doc->lCount = doc->m_vData.size();


Вот тут явно видно что вы "потенциаьлно" обращаетесь без объекта синхронизации к глобальный данным. Данные лучше передать по копии. Это так, нюанс.

На самом деле больше интересно: а в каком же месте падает? Если еще нет креш-дампера, самое время прикрутить
Re[10]: Проблема с потоками
От: stbzh  
Дата: 14.07.14 09:33
Оценка:
Здравствуйте, SmileIlya, Вы писали:
Сорри если пропустил и уже упомянули, но отличие SendMessage от PostMessage в том что первый — синхронный и блокирует основной поток обработки оконного класса (когда ты компилируешь приложение с флагом platform windows, тебе дается этот самый гуишный цикл обработки).
Вообще люди ж уже почти все разжевали здесь. ИМХО, (давать советы дело неблагодарное, но всеж рискну) тебе стоит немного отвлечься на другую задачу, так как создается впечатление что немного зациклился.
И почитать немного корифеев — лично мне в свое время здорово помогли статьи Joseph M. Newcomer. Вот эта, к примеру, по теме: http://www.flounder.com/workerthreads.htm
Re[10]: Проблема с потоками
От: MTimur  
Дата: 14.07.14 09:36
Оценка: -1
Здравствуйте, SmileIlya, Вы писали:

SI>К сожалению это действительно так. Падение не прекратилось.

SI>Каким образом это переделать у меня закончилась фантазия, может будут какие-то предложения?

В данном случае между потоками строку не нужно таскать. Одно сообщение передаст в GUI-поток m_lCountExec (причем, всего один раз при старте ThreadLoadProcMainThread), второе — lCount (после каждого изменения). А строку форматировать и заталкивать в статус-бар или еще куда нужно в потоке GUI'я.
Re[11]: Проблема с потоками
От: MTimur  
Дата: 14.07.14 09:39
Оценка:
Здравствуйте, MTimur, Вы писали:


MT>В данном случае между потоками строку не нужно таскать. Одно сообщение передаст в GUI-поток m_lTotalCount (причем, всего один раз при старте ThreadLoadProcMainThread), второе — lProcessedCount (после каждого изменения). А строку форматировать и заталкивать в статус-бар или еще куда нужно в потоке GUI'я.

fixed.
Re[12]: Проблема с потоками
От: MTimur  
Дата: 14.07.14 09:53
Оценка:
Здравствуйте, MTimur, Вы писали:

MT>>В данном случае между потоками строку не нужно таскать. Одно сообщение передаст в GUI-поток m_lTotalCount (причем, всего один раз при старте ThreadLoadProcMainThread), второе — lProcessedCount (после каждого изменения). А строку форматировать и заталкивать в статус-бар или еще куда нужно в потоке GUI'я.

MT>fixed.

Посмотрел повнимательнее код. Собственно даже эти данные передавать не нужно, они и так в CMyDoc находятся. Достаточно тупо периодически обновлять текст из основного потока (по таймеру или в ответ на "пустое" сообщение от рабочего потока)
Re[10]: Проблема с потоками
От: Кодт Россия  
Дата: 14.07.14 10:05
Оценка:
Здравствуйте, SmileIlya, Вы писали:

SI>Каким образом это переделать у меня закончилась фантазия, может будут какие-то предложения?


Два способа.

Первый — сделать рабочий поток полностью ведомым объектом.
То есть, пусть у нас есть данные, которые поток перемалывает, и к этим данным несколько функций доступа:
— из процедуры потока, — что-то там молоть
— из откуда угодно, — получить текущий статус (можно в виде строки)
Естественно, публичные данные защищены критической секцией. Важно, что время пребывания в критической секции должно быть детерминированным (никаких ожиданий, никаких синхронных вызовов наподобие того же SendMessage) и коротким. Т.е. рабочий поток не должен зайти в секцию и устроить там числодробилку на несколько секунд: нет, он зашёл, нахватался нужных данных, вышел, и грызёт их сколько влезет; потом зашёл, покидал результаты, вышел.
Процедура потока с какой-то периодичностью посылает совершенно асинхронное сообщение в оконный поток (можно — к конкретному окну): "у меня обновился статус, посмотри".
Окно, когда захочет (может — вообще по таймеру, а не по намёкам от рабочего) лезет в публичные данные, возможно, на несколько микросекунд спотыкается об критическую секцию, берёт, выходит и показывает в окошках.


Второй способ — сделать ведомым оконный поток.
Это более запарно, поскольку у него штатная синхронизация — это очередь оконных сообщений.
Использовать критические секции нельзя, я уже объяснил выше почему. Можно использовать синхрообъекты ядра (тяжёлые мьютексы, семафоры и т.п.), но их ожидание следует делать с помощью MsgWaitForMultipleObjects.
Перекуём баги на фичи!
Re[10]: Проблема с потоками
От: sidorov18 США  
Дата: 14.07.14 11:47
Оценка:
Здравствуйте, SmileIlya, Вы писали:

C>>> g_pDoc->m_pChild->SetStatusText( szTemp );

C>>>//заменить на
C>>> ::SendMessage(HWND_of_m_pChild,...)

SI>К сожалению это действительно так. Падение не прекратилось.

SI>Каким образом это переделать у меня закончилась фантазия, может будут какие-то предложения?

А в каком месте падает?
Код Ваш смахивает на творение новичка в программировании, посему предположу, что Вы могли передать указатель на локальную переменную szTemp. Не Ваш вариант?
Re[3]: Проблема с потоками
От: sidorov18 США  
Дата: 14.07.14 11:58
Оценка:
Здравствуйте, SmileIlya, Вы писали:

doc->>m_sema->Lock();

SI> CWinThread* thread = AfxBeginThread(
SI> CCrPercentDoc::ThreadLoadProcSecondThread,
SI> (LPVOID)(*it));

В качестве оффтопа еще рекомендую для таких задач использовать какую нибудь готовую реализацию пула потоков. Он создаст и оптимальное количество потоков( к примеру — кратное количеству ядер ) и не будет постоянно создавать и завершать потоки в целях оптимизации, как в Вашем случае.
Re: Проблема с потоками
От: AleksandrN Россия  
Дата: 15.07.14 11:16
Оценка:
Здравствуйте, SmileIlya, Вы писали:

SI>P.S. Мозг закипает.


Сделай пул потоков в общей очередью.

Поток, который закончил обрабатывать данные, должен вызвать PostMessage() и отправить сообщение окну, которое отображает данные. Затем поток должен взять из очереди новые данные для обработки. Если данных нет — закрыть семафор для этого потока и ждать, пока он освободится.

После добавления данных в очередь — открыть семафор для потоков, которые ничего не делают.

Для работы с очередью — создать мьютекс. Для приостановки работы потоков, которые ничего не делают — по одному семафору на поток.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.