Помогите в организации многопоточности

 
0
 
C++
ava
xTr1m | 02.10.2013, 09:27
День добрый, есть такая задача. Я запускаю один поток, который заполняет некий список. Также я запускаю несколько потоков (функция-обработчик у них одна), которые из этого списка значения тягают. Самой простой вариант этих двух функций выглядит как-то так

void CThreadsDlg::AddToList(void)
{
    CString valueStr;
    static int value = 0;

    while(true)
    {
        if(m_listValues.GetItemCount() <= 25)
        {
            valueStr.Format("%d", value++);

            {
                lock_guard<mutex> guard(m_list1Mutex);
                m_listValues.InsertItem(0, valueStr);
            }
        }        

        Sleep(100);
    }
}

// тут берется значение из одного списка и вставляется в другой
void CThreadsDlg::GetFromToList(void)
{
    CString valueStr;

    while(m_stopAll == false)
    {        
        {

            {
                lock_guard<mutex> guard(m_list1Mutex);

                if(m_listValues.GetItemCount() > 0)
                {
                    valueStr = m_listValues.GetItemText(m_listValues.GetItemCount() - 1, 0);
                    m_listValues.DeleteItem(m_listValues.GetItemCount() - 1);
                    m_resultList.InsertItem(0, valueStr); 
                }
            }
        }        

        Sleep(300);
    }
}


Но, как я понимаю, тут есть проблема (может и не одна), которая заключается в том, что потоки, которые "обрабатывают" значения из главного списка, работают постоянно. То есть бесконечный цикл, постоянно проверяет кол-во элементов на > 0. А ведь это процессорное время и т.п. Тогда я подумал сделать какое-нибудь событие. Типа поток, который AddToList, добавляет в список элемент и сигнализирует,  "мол в списке что-то появилось, давайте, обрабатывайте". Но тогда в каком месте нужно ставить событие в состояние "список пуст, больше брать нельзя". Что мне приходит на ум:
1. добавить это в функцию GetFromToList, но ведь это просто обработчик, зачем ему проверять пуст ли список и создавать какое-то событие, к тому же его тоже нужно блокировать мьютексом, что не было одновременно можно-нельзя.
2. можно завести еще один поток, который бы синхронизировал этот момент. Но это тоже какой-то огород получается.
3. может есть другое решение?

Спасибо.
Comments (12)
ava
cppGhost | 02.10.2013, 18:58 #
Мммм, то ли я плохо сформулировал вопрос, то ли это большой секрет =))
Переформулирую на каком-нибудь примере:
на сервер поступают запросы (это первый поток). на сервере запущено несколько потоков, которые обрабатывают, поступившие запросы.
Список запросов представляет собой вектор. Как мне кажется, неправильно делать так, чтобы эти потоки раз в N-секунд смотрели не пуст ли
вектор запросов. Нужно этим потокам как то сообщать, что вектор уже не пуст. И так же нужно как то сообщать им, что запросы все обработаны
и нужно подождать.

Ну как то так. У меня не такая задача. У меня задача понять, как это грамотно реализуют.
ava
xvr | 03.10.2013, 12:42 #
Цитата (cppGhost @  2.10.2013,  18:58 findReferencedText)
 У меня задача понять, как это грамотно реализуют. 

Грамотно это реализуют применением примитивов синхронизации, например семафором и/или событий.
Например семафор (который со счетчиком) считает количество элеменов в списке. При помещении в список семафор увеличивают на 1, при попытке чтения - ждут на нем. Если в списке есть элементы - ожидание завершается и счетчик автоматически уменьшается. Если же элементов нет, то поток чтения блокируется, пока элементы не появятся
ava
xTr1m | 10.10.2013, 13:48 #
Почитал еще литературу на эту тему и нашел то, что нужно в новом стандарте. Выглядит примерно так:


std::condition_variable CThreadsDlg::m_condition;

void CThreadsDlg::AddToList(void)
{
    CString valueStr;
    static int value = 0;
    while(true)
    {
        if(m_listValues.GetItemCount() <= 25)
        {
            valueStr.Format("%d", value++);
            {
                lock_guard<mutex> guard(m_list1Mutex);
                m_listValues.InsertItem(0, valueStr);
                m_condition.notify_one();
            }
        }        
        Sleep(100);
    }
}
// тут берется значение из одного списка и вставляется в другой
void CThreadsDlg::GetFromToList(void)
{
    CString valueStr;
    while(m_stopAll == false)
    {        
        {            
                lock_guard<mutex> guard(m_list1Mutex);
                m_condition.wait(guard, CThreadsDlg::IsListEmpty, this);
                
                valueStr = m_listValues.GetItemText(m_listValues.GetItemCount() - 1, 0);
                m_listValues.DeleteItem(m_listValues.GetItemCount() - 1);
                m_resultList.InsertItem(0, valueStr);                             
        }
        
        Sleep(300);
    }
}

то есть в потоке, который считывает значения будет захвачен мьютекс, проверен результат IsListEmpty, если true то функция GetFromList пойдет дальше. Если false, то мьютекс освобождается и дальше идет ожидание вызова notify_one() как сигнала к еще одной проверке.

Понятно, что гуру и так все знали =)) , но расписал я тут, чтобы самому еще раз это осмыслить, да и может кому то пригодится.

xvr, за идею тоже спасибо, но с событиями наверное будет лучше. А может нет, тогда просьба рассказать почему?)
ava
akizelokro | 10.10.2013, 13:55 #
Надо смотреть, как работают при многопоточности контейнеры стандартной библиотеки.
Что-то такое читал, что то ли в стандарте контейнеры не safe-threaded, а в Intel Threading Blocks проблема решена. Но надо уточнять. 
ava
bsa | 10.10.2013, 14:10 #
STL контейнеры не реализуют потокобезопасность.
ava
akizelokro | 10.10.2013, 16:10 #
Цитата (bsa @  10.10.2013,  14:10 findReferencedText)
STL контейнеры не реализуют потокобезопасность. 

Я ставил tbb месяца 4 назад чтоли, мне надо было под винду портировать одну из замороченных библиотек на основе компьютерного зрения. Там использовался этот tbb и в нем как-то потокобезопасность для элементарных контейнеров, кажется, была реализована (интеловская кроссплатформенная библиотека для потоков).
Так, на всякий случай информация
ava
akizelokro | 10.10.2013, 16:36 #
Вообще, по теме самого вопроса, более правильно, думаю, если не использовать потокобезопасную версию с этим tbb, то желательно бы "делать остановку" (mutex, auto, семафор, критическую секцию) полностью


        if(m_listValues.GetItemCount() <= 25)
        {
            valueStr.Format("%d", value++);
            {
                m_listValues.InsertItem(0, valueStr);
            }
        }        

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

Если интересна загрузка различных потоков с различными приоритетами выполнения, то это, по-моему, тоже делается.
ava
bsa | 11.10.2013, 00:01 #
akizelokro, в спецификации STL указано, что контейнеры потокобезопасны для чтения, но для записи нет. То, что кто-то сделал их безопасные версии, я не сомневаюсь. Но когда ты используешь что-то нестандартное, то ты знаешь зачем и что это дает.  smile 
ava
akizelokro | 11.10.2013, 07:09 #
Энто, конечно, да.
Тут даже если пофлудить немного ненавязчиво захочется, много не попищещь.
Поэтому внушает  smile 
ava
xvr | 11.10.2013, 11:40 #
Цитата (xTr1m @  10.10.2013,  13:48 findReferencedText)
Выглядит примерно так:

Рассмотрим такой сценарий -
  •  Вызывается GetFromToList
  •  Захват мьютекса в строке 28
  •  Блок на ожидании m_condition в строке 29 - поток чтения ждет
  •  Параллельный поток (записи) вызывает AddToList
  •  Попытка захватить мьютекс в строке 13, блок, т.к. мьютекс уже захвачен потоком чтения
  •  До строки 14, где должна была установиться m_condition поток записи не доходит
  •  Результат - потоки записи и чтения ждут друг друга, полный deadlock системы
Совет - строки 28 и 29 поменять местами.

Цитата (xTr1m @  10.10.2013,  13:48 findReferencedText)
но с событиями наверное будет лучше.

Не будет. Пример:
Конфигурация потоков -
  •  Поток записи пишет 2 элемента, потом ждет сообщения от потока чтения
  •  Поток чтения читает 2 элемента, потом посылает сообщение потоку записи
в такой конфигурации потоки должны работать без deadlock'ов

Сценарий исполнения -
  •  Поток записи пишет 2 элемента (за раз). m_condition взводится.
  •  Поток записи ожидает сигнал
  •  Поток чтения входит в GetFromToList, забирает элемент, m_condition сбрасывается.
  •  Поток чтения снова вызывает GetFromToList (т.к. ему надо прочесть 2 значения), но т.к. m_condition сброшена, он блокируется на ее ожидании.
  •  Результат - потоки записи и чтения ждут друг друга, полный deadlock системы
ava
xTr1m | 11.10.2013, 19:25 #
xvr, пример я брал из книги. попробовать это на компе пока не было времени, но что я понял из описания. В строке 28 захватывается мьютекс, в строке 29 проверяется возвращаемое значение CThreadsDlg::IsListEmpty. Если оно false, то мьютекс освобождается, а сам поток ожидает вызова notify_one для m_condition и deadlock'а вроде быть не должно.
ava
xvr | 12.10.2013, 15:17 #
Цитата (xTr1m @  11.10.2013,  19:25 findReferencedText)
ждать их он не должен.


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

Цитата (xTr1m @  11.10.2013,  19:25 findReferencedText)
xvr, пример я брал из книги.

Это еще не гарантия того, что он рабочий  smile 

Цитата (xTr1m @  11.10.2013,  19:25 findReferencedText)
попробовать это на компе пока не было времени,

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

Цитата (xTr1m @  11.10.2013,  19:25 findReferencedText)
 В строке 28 захватывается мьютекс, в строке 29 проверяется возвращаемое значение CThreadsDlg::IsListEmpty. 

Ага, да, в ожидание conditional variable mutex попадает. Тогда Deadlock'а по первому сценарию не будет, но по второму остается (либо dedlock либо потеря данных)
Please register or login to write.
Firm of day
Вы также можете добавить свою фирму в каталог IT-фирм, и публиковать статьи, новости, вакансии и другую информацию от имени фирмы.
Подробнее
Contributors
  xTr1m ava  bsa   akizelokro   xvr   cppGhost
advanced
Submit