Re[6]: Многопоточный Observer на C++ (практика)
От: landerhigh Пират  
Дата: 26.11.10 00:49
Оценка: 1 (1) +5
Здравствуйте, Ryadovoy, Вы писали:

U>>даже если вы не переносите исключения, то вы все равно могли бы сделать код более качественным с точки зрения поддержки

U>>в этом вам помог бы механизм RAII
R>Этот механизм все же "на любителя"

Это общепринятый механизм.

Ручные EnterCriticaLSection, LeaveCriticalSection и иже с ними у нас являются поводом к прекращени code review и отправкой его на доработку.
www.blinnov.com
Re[3]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 25.11.10 14:22
Оценка: 8 (2) +2 :)
Здравствуйте, uzhas, Вы писали:

R>>Это, конечно, достаточно печально. Выделять память, копировать N памяти, атомарно инкрементировать N счётчиков под мьютексом не айс.

U>ничего печального нет в этом
U>просто вы озабочены лок-фри алгоритмами и оптимизациями
U>статья далека от ваших заумностей, но вполне подходит для решения простых задач даже в корпоративном софте

Это не повод заниматься предварительной пессимизацией и неправильно проектировать.
Один подписчик обычно обрабатывает множество уведомлений. Отсюда следует, что копировать контейнер и захватывать ссылки лучше при добавлении/удалении подписчиков, а не при нотификациях. Соотв. ты просто делаешь что-то типа такого. И никакой тебе лок-фри магии. Нотификации всегда O(1).

    typedef std::vector<INotificationListenerPtr> CNotificationListenerPtrList;
    typedef boost::shared_ptr<CNotificationListenerPtrList> CNotificationListenerPtrListPtr;

    CNotificationListenerPtrListPtr m_vListeners;
    
    bool Subscribe(INotificationListenerPtr pListener)
    {
        Lock();
        for(size_t i = 0; i < m_vListeners->size(); ++i)
        {
            if((*m_vListeners)[i].px == pListener.px)
            {
                Unlock();
                return false;
            }
        }
        CNotificationListenerPtrListPtr copy (new CNotificationListenerPtrList(*m_vListeners));
        copy->push_back(pListener);
        m_vListeners.swap(copy);
        Unlock();
        return true;
    }

    void SendNotification(void* pContext)
    {
        Lock();
        CNotificationListenerPtrListPtr vListeners = m_vListeners;
        Unlock();

        for(size_t i = 0; i < vListeners->size(); ++i)
        {
            (*vListeners([i]->OnNotification(pContext);
        }
    }





1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 13:31
Оценка: 1 (1) -1 :)))
Здравствуйте, rg45, Вы писали:

R>Общая идея понятна. Детально код не изучал, но что сразу резануло по глазам — небезопасность кода с точки зрения исключений — повсеместно в коде встречаются фрагменты:

R>
R>EnterCriticalSection();
R>//Какие-то операции
R>LeaveCriticalSection();
R>

R>Что если во время выполнения "каких-то операций" возникнет исключение? Почему бы в данном случае не использовать RAII — например, мьютексы и локи из boost::thread?

Вы конечно-же со мной не согласитесь, но я считаю что c++ исключения это зло.
Я работаю в команде, где не особо ими пользуются (только при необходимости), а необходимости из под колбека выбрасывать исключение нет никакой.
bools и stl может выбросить исключение, но оно возможно лишь когда имеет место ошибка программиста
(ну или закончилась память в системе, что тоже критично), тогда нужно падать с креш репортом, дабы не скрывать ошибку.
Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 12:53
Оценка: 5 (2)
Всем привет, хочу вот поделится наработками, интересно имеет ли право на жизнь такая статья, или смысла нет ибо "еще один велосипед"?
Многопоточный Observer на C++ (практика)
Есть много вариаций на тему данного паттерна, но большинство примеров, найденных мною, не подходит для многопоточных приложений.
В этой статье я хочу поделится опытом применения паттерна в многопоточных приложениях и опишу основные проблемы, с которыми мне пришлось столкнуться.
Словарь предметной области.
Для начала давайте разберемся со словарем предметной области.
И так, действующие лица
Издатель, рассылающий уведомления — NotificationsDispatcher
Подписчик, получающий уведомления — NotificationsListener
Взаимодействие учасников
Подписка — Subscribe
Прекращение подписки — Unsubscribe
Отправка и получение сообщений — SendNotification и OnNotification
Также необходимо учитывать время жизни объектов-подписчиков и объекта-издателя.
Простейшая реализация для однопоточной среды.
class INotificationListener
{
public:
    virtual void OnNotification(void* pContext) = 0;
};

class CNotificationDispatcher
{
public:
    bool Subscribe(INotificationListener* pListener)
    {
        for(size_t i = 0; i < m_vListeners.size(); ++i)
        {
            if(m_vListeners[i] == pListener)
            {
                return false;
            }
        }
        m_vListeners.push_back(pListener);
        return true;
    }
    bool Unsubscribe(INotificationListener* pListener)
    {
        for(size_t i = 0; i < m_vListeners.size(); ++i)
        {
            if(m_vListeners[i] == pListener)
            {
                m_vListeners.erase(m_vListeners.begin() + i);
                return true;
            }
        }
        return false;
    }
    void SendNotification(void* pContext)
    {
        for(size_t i = 0; i < m_vListeners.size(); ++i)
        {
            m_vListeners[i]->OnNotification(pContext);
        }
    }
private:
    std::vector<INotificationListener*> m_vListeners;
};

Пример использования
class CNotificationListener:
    public INotificationListener
{
    virtual void OnNotification(void* pContext)
    {
        wprintf(L"%d\n", *((int*)pContext));
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    CNotificationDispatcher aDispatcher;

    CNotificationListener aListener1;
    CNotificationListener aListener2;
    CNotificationListener aListener3;

    aDispatcher.Subscribe(&aListener1);
    aDispatcher.Subscribe(&aListener2);
    aDispatcher.Subscribe(&aListener3);

    for(int i = 0; i < 5; ++i)
    {
        aDispatcher.SendNotification(&i);
    }

    aDispatcher.Unsubscribe(&aListener2);
    aDispatcher.Unsubscribe(&aListener1);
    aDispatcher.Unsubscribe(&aListener3);

    return 0;
}

Переходим к многопоточной среде.
С одним потоком такой код будет работать довольно стабильно.
Давайте посмотрим что будет при работе нескольких потоков.
DWORD WINAPI SendNotificationThread(PVOID pParam)
{
    CNotificationDispatcher* pDispatcher = (CNotificationDispatcher*)pParam;
    for(int i = 0;;++i)
    {
        pDispatcher->SendNotification(&i);
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    CNotificationDispatcher aDispatcher;
    ::CreateThread(NULL, 0, SendNotificationThread, &aDispatcher, 0, NULL);

    CNotificationListener aListener1;
    CNotificationListener aListener2;
    CNotificationListener aListener3;

    for(;;)
    {
        aDispatcher.Subscribe(&aListener1);
        aDispatcher.Subscribe(&aListener2);
        aDispatcher.Subscribe(&aListener3);
        
        aDispatcher.Unsubscribe(&aListener2);
        aDispatcher.Unsubscribe(&aListener1);
        aDispatcher.Unsubscribe(&aListener3);
    }

    return 0;
}

Если запустить такой код на выполнение, то рано или позно произойдет креш.
Проблема заключается в добавлении/удалении подписчиков и одновременной рассылке уведомлений (многопоточный доступ к CNotificationDispatcher::m_vListeners в нашем примере).
Здесь необходима синхронизация доступа к списку подписчиков CNotificationDispatcher::m_vListeners.
Синхронизация доступа к списку подписчиков.
class CNotificationDispatcher
{
public:
    CNotificationDispatcher()
    {
        InitializeCriticalSection(&m_cs);
    }
    ~CNotificationDispatcher()
    {
        DeleteCriticalSection(&m_cs);
    }
public:
    bool Subscribe(INotificationListener* pListener)
    {
        Lock();
        for(size_t i = 0; i < m_vListeners.size(); ++i)
        {
            if(m_vListeners[i] == pListener)
            {
                Unlock();
                return false;
            }
        }
        m_vListeners.push_back(pListener);
        Unlock();
        return true;
    }
    bool Unsubscribe(INotificationListener* pListener)
    {
        Lock();
        for(size_t i = 0; i < m_vListeners.size(); ++i)
        {
            if(m_vListeners[i] == pListener)
            {
                m_vListeners.erase(m_vListeners.begin() + i);
                Unlock();
                return true;
            }
        }
        Unlock();
        return false;
    }
    void SendNotification(void* pContext)
    {
        Lock();
        for(size_t i = 0; i < m_vListeners.size(); ++i)
        {
            m_vListeners[i]->OnNotification(pContext);
        }
        Unlock();
    }
private:
    void Lock()         {EnterCriticalSection(&m_cs);}
    void Unlock()       {LeaveCriticalSection(&m_cs);}
private:
    std::vector<INotificationListener*> m_vListeners;
    CRITICAL_SECTION                    m_cs;
};

При такой реализации данный код не будет падать при использовании в предыдущем примере, но нас поджидает еще одна неприятная ситуация.
Борьба с взаимной блокировкой потоков (deadlock).
Допустим у нас есть некий поток, выполняющий какую-то фоновую задачу и есть окно, где отображается ход выполнения этой задачи.
Как правило поток посылает уведомление классу окна, который в свою очередь вызывает SendMessage и делает какие-то действия в оконной процедуре.
Функция SendMessage является блокирующей, и работает она следующим образом — посылает уведомление потоку окна и ждет пока тот его обработает.
Если подключение/отключение подписчика будет происходить так-же в оконной процедуре (в контексте потока окна) возможна взаимная блокировка потоков, так называемый deadlock.
Такой deadlock может воспроизоводится крайне редко (в момент вызова Subscribe/Unsubscribe и одновременном вызове OnNotification в отдельном потоке)
Следующий код эмулирует данный deadlock.
CRITICAL_SECTION    g_cs;
class CNotificationListener:
    public INotificationListener
{
    virtual void OnNotification(void* pContext)
    {
        //Эмулируем блокирующий вызов SendMessage
        EnterCriticalSection(&g_cs);

        wprintf(L"%d\n", *((int*)pContext));

        LeaveCriticalSection(&g_cs);
    }
};

DWORD WINAPI SendNotificationThread(PVOID pParam)
{
    CNotificationDispatcher* pDispatcher = (CNotificationDispatcher*)pParam;
    for(int i = 0;;++i)
    {
        pDispatcher->SendNotification(&i);
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    InitializeCriticalSection(&g_cs);

    CNotificationDispatcher aDispatcher;
    ::CreateThread(NULL, 0, SendNotificationThread, &aDispatcher, 0, NULL);

    CNotificationListener aListener1;
    for(;;)
    {
        //Эмулируем контекст оконной процедуры (обработчик оконного сообщения)
        EnterCriticalSection(&g_cs);

        aDispatcher.Subscribe(&aListener1);
        aDispatcher.Unsubscribe(&aListener1);

        LeaveCriticalSection(&g_cs);
    }

    return 0;
}

Проблема заключается в том, что главный поток захватывает глобальную критическую секцию g_cs (при аналогии с оконной процедурой — выполняется в контексте оконного потока), и затем вызывает метод Subscribe/Unsubscribe, который внутри захватывает CNotificationDispatcher::m_cs.
В этот момент рабочий поток посылает уведомление, захватив CNotificationDispatcher::m_cs, и затем пытается захватить глобальный g_cs (при аналогии с оконом — вызывает SendMessage).
Поток окна A -> B
Рабочий поток B -> A
Это можно назвать класическим deadlock-ом.
Проблема скрывается в реализации метода CNotificationDispatcher::SendNotification
Мы не должны отсылать уведомления (вызывать callback), захватив при этом какой-либо синхронизационный объект, котрый может быть захвачен в обработчике уведомления (напрямую или косвенно). В реальном проекте таких ситуаций может быть множество, и разобраться с ними порой довольно сложно.
И так, убираем блокировку при вызове уведомлений
void SendNotification(void* pContext)
{
    Lock();
    std::vector<INotificationListener*> vListeners = m_vListeners;
    Unlock();

    for(size_t i = 0; i < vListeners.size(); ++i)
    {
        vListeners[i]->OnNotification(pContext);
    }
}

Контроль времени жизни подписчиков.
После того, как мы убрали deadlock при вызове функции OnNotification у нас появилась другая проблема — время жизни объектов-подписчиков.
У нас больше нет гарантии, что метод OnNotification не будет вызван после вызова Unsubscribe, и по этому мы не можем удалить объект-подписчик непосредственно после вызова Unsubscribe.
В данной ситуации проще всего контролировать время жизни объектов-подписчиков с использованием счетчика ссылок.
Для этого можно исползовать технологию COM — унаследовать интерфейс INotificationListener от IUnknown и использовать ATL CComPtr для списка подписчиков внутри класса CNotificationDispatcher, тоесть заменить std::vector<INotificationListener*> на std::vector<CComPtr<INotificationListener>>.
Но такая реализация чревата дополнительными расходами на реализацию классов-подписчиков, так как в каждом из них должны быть реализованы методы AddRef/Release.
Для контроля времени жизни подписчиков с исползованием счетчика ссылок хорошо подойдут умные указатели.
Финальная версия.
Нижележащий код будет финальной версией в данном обзоре, но это далеко не идеальная реализация, так как нет пределу совершенства.
Также в каждом конкретном случае возможны вариации, универсальное решение не всегда лучше.
class INotificationListener
{
public:
    virtual void OnNotification(void* pContext) = 0;
};

typedef boost::shared_ptr<INotificationListener> INotificationListenerPtr;

class CNotificationDispatcher
{
private:
    typedef std::vector<INotificationListenerPtr> CNotificationListenerPtrList;
public:
    CNotificationDispatcher()
    {
        InitializeCriticalSection(&m_cs);
    }
    ~CNotificationDispatcher()
    {
        DeleteCriticalSection(&m_cs);
    }
public:
    bool Subscribe(INotificationListenerPtr pListener)
    {
        Lock();
        for(size_t i = 0; i < m_vListeners.size(); ++i)
        {
            if(m_vListeners[i].px == pListener.px)
            {
                Unlock();
                return false;
            }
        }
        m_vListeners.push_back(pListener);
        Unlock();
        return true;
    }
    bool Unsubscribe(const INotificationListener* pListener)
    {
        Lock();
        for(size_t i = 0; i < m_vListeners.size(); ++i)
        {
            if(m_vListeners[i].get() == pListener)
            {
                INotificationListenerPtr toRelease = m_vListeners[i];
                m_vListeners.erase(m_vListeners.begin() + i);
                Unlock();
                toRelease.reset();
                return true;
            }
        }
        Unlock();
        return false;
    }
    void SendNotification(void* pContext)
    {
        Lock();
        CNotificationListenerPtrList vListeners = m_vListeners;
        Unlock();

        for(size_t i = 0; i < vListeners.size(); ++i)
        {
            vListeners[i]->OnNotification(pContext);
        }
    }
private:
    void Lock()         {EnterCriticalSection(&m_cs);}
    void Unlock()       {LeaveCriticalSection(&m_cs);}
private:
    CNotificationListenerPtrList    m_vListeners;
    CRITICAL_SECTION                m_cs;
};

Я заменил "голый" указатель INotificationListener* на "умный" указатель со счетчиком ссылок, такой оказался в библиотеке boost.
В функции Unsubscribe входной параметр используется исключительно как идентификатор отключаемого объекта, по этому там можно оставить просто указатель.
Также в функции Unsubscribe я добавил переменную toRelease для того, чтобы вызвать деструктор подписчика уже после вызова Unlock
Стоит обратить внимание на то, что в функции SendNotification происходит копирование списка умных указателей (после копирования все указатели увеличивают свои счетчики ссылок, а при выходе из функции уменьшают, что и контролирует время жизни подписчиков)
Тестируем.
CNotificationDispatcher g_Dispatcher;

CRITICAL_SECTION    g_cs;

class CNotificationListener:
    public INotificationListener
{
public:
    CNotificationListener(int id)
        :m_id(id)
    {
    }
    ~CNotificationListener()
    {
    }
    virtual void OnNotification(void* pContext)
    {
        //Эмулируем блокирующий вызов SendMessage
        EnterCriticalSection(&g_cs);

        wprintf(L"%d\n", *((int*)pContext));

        if(m_id == 3)
        {
            //отписываемся прямо из callback-а
            g_Dispatcher.Unsubscribe(this);
        }

        LeaveCriticalSection(&g_cs);
    }
    int m_id;
};

DWORD WINAPI SendNotificationThread(PVOID pParam)
{
    CNotificationDispatcher* pDispatcher = (CNotificationDispatcher*)pParam;
    for(int i = 0;;++i)
    {
        pDispatcher->SendNotification(&i);
        Sleep(0);
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    InitializeCriticalSection(&g_cs);

    ::CreateThread(NULL, 0, SendNotificationThread, &g_Dispatcher, 0, NULL);
    ::CreateThread(NULL, 0, SendNotificationThread, &g_Dispatcher, 0, NULL);
    ::CreateThread(NULL, 0, SendNotificationThread, &g_Dispatcher, 0, NULL);

    for(;;)
    {
        //Эмулируем контекст оконной процедуры (обработчик оконного сообщения)
        EnterCriticalSection(&g_cs);
        
        INotificationListenerPtr pListener1(new CNotificationListener(1));
        INotificationListenerPtr pListener2(new CNotificationListener(2));
        
        g_Dispatcher.Subscribe(pListener1);
        g_Dispatcher.Subscribe(pListener2);
        g_Dispatcher.Subscribe(INotificationListenerPtr(new CNotificationListener(3)));


        Sleep(0);

        g_Dispatcher.Unsubscribe(pListener1.get());
        
        INotificationListener* idToUnsubscribe = pListener2.get();
        pListener2.reset();
        g_Dispatcher.Unsubscribe(idToUnsubscribe);

        LeaveCriticalSection(&g_cs);
        
        Sleep(100);
    }

    return 0;
}
observer многопоточный c++
Re[6]: Многопоточный Observer на C++ (практика)
От: Юрий Жмеренецкий ICQ 380412032
Дата: 25.11.10 16:01
Оценка: 2 (2)
Здравствуйте, Ryadovoy, Вы писали:

ЮЖ>>Здесь не нужна обработка нехватки памяти. Только RAII для lock/unlock. Как миниму в силу того, что на момент написания неизвестны сценарии использования.


R>Рои не панацея, и иногда уменьшают читабельность и понимание происходящего.



R>1) без роев

R>
R>void SendNotification(void* pContext)
R>{
R>    Lock();
R>    CNotificationListenerPtrList vListeners = m_vListeners;
R>    Unlock();

R>    for(size_t i = 0; i < vListeners.size(); ++i)
R>    {
R>        vListeners[i].OnNotification(pContext);
R>    }
R>}
R>

R>2) поменяли на рой, но допустили ошибку, она сразу заметна при сравнении с предыдущим кодом?
R>
R>void SendNotification(void* pContext)
R>{
R>    ScopeLocker scopeLocker(&m_cs);
R>    CNotificationListenerPtrList vListeners = m_vListeners;

R>    for(size_t i = 0; i < vListeners.size(); ++i)
R>    {
R>        vListeners[i].OnNotification(pContext);
R>    }
R>}
R>

R>3) а надо было вот так.
R>
R>void SendNotification(void* pContext)
R>{
R>    CNotificationListenerPtrList vListeners;
R>    {
R>        ScopeLocker scopeLocker(&m_cs);
R>        vListeners = m_vListeners;
R>    }
R>    for(size_t i = 0; i < vListeners.size(); ++i)
R>    {
R>        vListeners[i].OnNotification(pContext);
R>    }
R>}
R>



Сравниваемые примеры (1 и 3) не равноценны. Аналог для третьего примера должен выглядеть как-то так:

void SendNotification(void* pContext)
{
    CNotificationListenerPtrList vListeners;

    Lock();

    try 
    {
        vListeners = m_vListeners;
    }
    catch(const std::exception&)
    {
        Unlock();
        throw;
    }

    Unlock();

    for(size_t i = 0; i < vListeners.size(); ++i)
    {
        vListeners[i].OnNotification(pContext);
    }
}


Каким образом переписывание такого кода на вариант с использованием RAII уменьшит читабельность?
+ потенциальных мест для ошибок в этом коде больше чем в аналоге с RAII.
+
Re[6]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 25.11.10 14:42
Оценка: 1 (1) +1
Здравствуйте, Ryadovoy, Вы писали:

R>Мне попадался проект в котором сплошь и рядом встречались конструкции try{}catch(...){} которые прятали реальные баги.

R>программа не падала, но глючила страшно.
R>Удалось ее нам привести к более менее стабильному состоянию лишь убрав все эти перехватчики.

Бесспорно бывают решения ещё хуже.
Вопрос только, сравнить свою программу с гораздо более плохой и сказать "Ну ничего у меня ещё не худший вариант". Или сравнить свою программу с более хорошей и сказать "А ведь можно сделать лучше".


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: Многопоточный Observer на C++ (практика)
От: rg45 СССР  
Дата: 25.11.10 13:18
Оценка: +2
Здравствуйте, Ryadovoy, Вы писали:

R>Всем привет, хочу вот поделится наработками, интересно имеет ли право на жизнь такая статья, или смысла нет ибо "еще один велосипед"?

R>...

Общая идея понятна. Детально код не изучал, но что сразу резануло по глазам — небезопасность кода с точки зрения исключений — повсеместно в коде встречаются фрагменты:
EnterCriticalSection();
//Какие-то операции
LeaveCriticalSection();

Что если во время выполнения "каких-то операций" возникнет исключение? Почему бы в данном случае не использовать RAII — например, мьютексы и локи из boost::thread?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[3]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 27.11.10 09:31
Оценка: +2
Здравствуйте, Ryadovoy, Вы писали:

R>Можно попробовать ожидать завершение текущего уведомления в функции Unsubscribe, но тогда скорей всего будут зависания при отключении.

R>Я пока что не нашел подходящее решение с реализацией Unsubscribe, гарантирующей прекращение нотификаций после выхода.
R>Если есть какие-то идеи по этому поводу, буду рад услышать.

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


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Многопоточный Observer на C++ (практика)
От: TarasKo Голландия  
Дата: 28.11.10 21:57
Оценка: +1 -1
Я очень много раз встречал отказ использовать исключения просто потому что люди не понимаю, как строит программы с обработкой ошибок на исключениях. На вопрос, в чём преимущество обработки ошибок через коды возврата, обычно некоторое время думают и говорят, мол типа один знакомый программист Вася, говорил что исключения тормозные, ну и как-то они запутывают код. Вы понимаете что такое ответ говорит лишь о том что человек не понимает исключений, не понимает как ими пользоваться, за предыдущее время работы не попытался их понять и научится ими пользоваться. И кстати если это время больше 2-х лет, то не стоит связываться с таким человеком и брать его на работу.

Лишь один раз я слышал действительно мотивированный отказ от построения обработки ошибок на исключениях, и это google code convention. Они отказываются от исключений в основном потому, что на момент написания code guidelines у них уже написано огромное количество кода с обработкой ошибок на кодах возврата. При этом перед этим идёт честное сравнение плюсов и минусов обеих подходов.
Re[2]: Многопоточный Observer на C++ (практика)
От: Юрий Жмеренецкий ICQ 380412032
Дата: 25.11.10 14:16
Оценка: 1 (1)
Здравствуйте, uzhas, Вы писали:


U>2) вам посоветовали исключить ручные Lock\Unlock, потому что это усложняет поддержку кода и может привести к проблемам в случае исключений имхо (хотя в конкретном коде я проблем не увидел)


Теоритически эти проблемы могут возникнуть при возбуждении std::bad_alloc при вставке в вектор (CNotificationDispatcher::Subscribe) :

typedef std::vector<INotificationListenerPtr> CNotificationListenerPtrList;
...
CNotificationListenerPtrList    m_vListeners;


Lock();
....
m_vListeners.push_back(pListener);
Unlock();


и при копировании (CNotificationDispatcher::SendNotification):

Lock();
CNotificationListenerPtrList vListeners = m_vListeners;
Unlock();
Re[6]: Многопоточный Observer на C++ (практика)
От: gegMOPO4  
Дата: 25.11.10 15:58
Оценка: 1 (1)
Здравствуйте, Ryadovoy, Вы писали:
R>2) поменяли на рой, но допустили ошибку, она сразу заметна при сравнении с предыдущим кодом?

Да, сразу.
Re: Многопоточный Observer на C++ (практика)
От: Alexander G Украина  
Дата: 25.11.10 13:24
Оценка: +1
Здравствуйте, Ryadovoy, Вы писали:

R> или смысла нет ибо "еще один велосипед"?


ну, когда мне такое понадобится, я скорее посмотрю на boost::signals2
Русский военный корабль идёт ко дну!
Re: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 25.11.10 14:07
Оценка: +1
Здравствуйте, Ryadovoy, Вы писали:

R>Простейшая реализация для однопоточной среды.


Это не будет работать. Достаточно в обработчике добавить/удалить несколько подписчиков, как всё крякнется.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Многопоточный Observer на C++ (практика)
От: uzhas Ниоткуда  
Дата: 25.11.10 14:13
Оценка: -1
Здравствуйте, remark, Вы писали:

R>Это, конечно, достаточно печально. Выделять память, копировать N памяти, атомарно инкрементировать N счётчиков под мьютексом не айс.

ничего печального нет в этом
просто вы озабочены лок-фри алгоритмами и оптимизациями
статья далека от ваших заумностей, но вполне подходит для решения простых задач даже в корпоративном софте
Re[4]: Многопоточный Observer на C++ (практика)
От: uzhas Ниоткуда  
Дата: 25.11.10 14:37
Оценка: -1
Здравствуйте, remark, Вы писали:

R>Это не повод заниматься предварительной пессимизацией и неправильно проектировать.

вместо одного push_back вы сделали три операции, на осознание которых (честно) у меня ушло 3 минуты
имхо, это называется предварительная оптимизация
да и употребление .px вместо .get() тоже намекает на вашу озабоченность
просто вы так долго варитесь в этом, что считаете это естественным, тогда как другие чаще всего работают на другом уровне
но я не хочу спорить об этом
R>Один подписчик обычно обрабатывает множество уведомлений. Отсюда следует, что копировать контейнер и захватывать ссылки лучше при добавлении/удалении подписчиков, а не при нотификациях. Соотв. ты просто делаешь что-то типа такого. И никакой тебе лок-фри магии. Нотификации всегда O(1).

я понял вашу идею, она мне тоже кажется оптимальнее

R>
:))))))))
От: rm822 Россия  
Дата: 25.11.10 23:57
Оценка: :)
для 90х это было бы ОК, но на дворе-то 2010й
зачем было разводить столько графоманства, когда современными средствами задача решается тривиально?
пруф-код, слепленый за 5 ммин

struct Subscriber {
    void OnSomething(int asContext) { printf("%i\n", asContext); }
};

int _tmain(int argc, _TCHAR* argv[])
{
    std::set<Subscriber*> subscribers;
    subscribers.insert(new Subscriber());

    int context = 7;
    task_group g;
    g.run( [subscribers,context] { for each(auto s in subscribers) { s->OnSomething(context); }} );
    g.wait();

    return 0;
}
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[7]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 26.11.10 10:36
Оценка: :)
Я думаю что все зависит от кривизны рук, а не от применяемого инструмента или от технологии.
Если разработчик с кривыми руками нужно его гнать в шею и нанять толкового программиста.
Не думайте что я не буду использовать RAII если они будут нужны,
как доказательство могу привести пример моего личного их использования
for(;;)
{
    {   //Enter to notify thread critical section
        CGuard aGuard(m_csNotifyThread);
        if(m_vFilesNotifyQueue.empty())
        {
            if(!str)
            {
                //Scan was completed
                CGuard aGuard(m_csScanState);
                m_nScanState = IFRClient::NotStarted;
            }
            break;
        }
    }

    if(WaitForSingleObject(m_hNotifyThread, 100) != WAIT_TIMEOUT)
    {
        //Something happends with notitification thread, that is error case
        _ASSERT(FALSE);
        CGuard aGuard(m_csScanState);
        m_nScanState = IFRClient::NotStarted;
        break;
    }
}
Re[7]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 26.11.10 10:56
Оценка: +1
Здравствуйте, landerhigh, Вы писали:

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


U>>>даже если вы не переносите исключения, то вы все равно могли бы сделать код более качественным с точки зрения поддержки

U>>>в этом вам помог бы механизм RAII
R>>Этот механизм все же "на любителя"

L>Это общепринятый механизм.


L>Ручные EnterCriticaLSection, LeaveCriticalSection и иже с ними у нас являются поводом к прекращени code review и отправкой его на доработку.


Я думаю что тематика RAII к данной статье притянута за уши, давайте создадим тему RAII и будем это там это обсуждать?

я так понимаю это вопрос веры для многих поклонников RAII,
по этому я подумаю над тем, чтобы добавить RAII в примеры,
дабы избежать в дальнейшем коментариев насчет RAII.
Re[3]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 26.11.10 19:09
Оценка: +1
Здравствуйте, Ryadovoy, Вы писали:

R>Можно попробовать ожидать завершение текущего уведомления в функции Unsubscribe, но тогда скорей всего будут зависания при отключении.

R>Я пока что не нашел подходящее решение с реализацией Unsubscribe, гарантирующей прекращение нотификаций после выхода.
R>Если есть какие-то идеи по этому поводу, буду рад услышать.

Это фундаметальная проблема такого подхода.
Решить её можно только перейдя к асинхронному обмену сообщениями. А именно с каждым объектом связывается понятие родительского потока, при нотификации родительским потокам рассылаются асинхронные сообщения. При их получении потоки проверяют, не отписан ли ещё каждый конкретный объект, если нет, то вызывается обработчик объекта. Соотв. отписывать можно только из родительского потока.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[6]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 27.11.10 10:05
Оценка: :)
Здравствуйте, baily, Вы писали:

R>>Это потенциальные дедлоки.


B>Решение рабочее. Дедлоков при аккуратной работе случаться не должно.


Никаких ошибок вообще случаться не должно.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[5]: Молодняк металла
От: alexeiz  
Дата: 30.11.10 16:55
Оценка: +1
Здравствуйте, COFF, Вы писали:

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


TK>>Лишь один раз я слышал действительно мотивированный отказ от построения обработки ошибок на исключениях, и это google code convention. Они отказываются от исключений в основном потому, что на момент написания code guidelines у них уже написано огромное количество кода с обработкой ошибок на кодах возврата. При этом перед этим идёт честное сравнение плюсов и минусов обеих подходов.


COF>Android NDK, например, не поддерживает исключения и RTTI. Во что превращается портирование кода в случае, если ты их используешь, думаю объяснять не надо ))


Исключения там не поддерживаются, потому что в Google их не используют. А не используют, потому что coding conventions такие. Иначе бы давно сделали. Это не так уж и сложно, как показывает следующий проект: http://www.crystax.net/android/ndk-r4.php
Re[6]: Молодняк металла
От: COFF  
Дата: 30.11.10 19:45
Оценка: +1
Здравствуйте, alexeiz, Вы писали:

COF>>Android NDK, например, не поддерживает исключения и RTTI. Во что превращается портирование кода в случае, если ты их используешь, думаю объяснять не надо ))


A>Исключения там не поддерживаются, потому что в Google их не используют. А не используют, потому что coding conventions такие. Иначе бы давно сделали. Это не так уж и сложно, как показывает следующий проект: http://www.crystax.net/android/ndk-r4.php


Это другой вопрос, на самом деле — почему это так? Если ты не гугл, конечно )) Факт, что на настоящий момент код с исключениями значительно менее портабелен, как это ни прискорбно
Re[2]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 13:41
Оценка:
Здравствуйте, Alexander G, Вы писали:
AG>ну, когда мне такое понадобится, я скорее посмотрю на boost::signals2
Спасибо за наводку, я обязательно обращу внимание на signals2.
Моя статья направлена не на то, чтобы дать готовое решение,
она объясняет основные принципы, которые применимы там, где boost бывает недоступен (драйвера, нейтивные приложения и т.п.)
Re: Многопоточный Observer на C++ (практика)
От: uzhas Ниоткуда  
Дата: 25.11.10 13:42
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Всем привет, хочу вот поделится наработками, интересно имеет ли право на жизнь такая статья, или смысла нет ибо "еще один велосипед"?

статья актуальна, поэтому я поддерживаю вашу инициативу
есть ряд замечаний, которые, как мне кажется, вполне уместны
1) в коде много Windows-specific кода, мне кажется, что это отвлекает от сути подхода
общеупотребительные (типа буста) или ваши собственные абстракции, скрывающие взаимодействие с операционкой были бы уместны.
пример:
class Mutex
{
public:
  void Lock();
  void Unlock();
};

class MutexLock //: non_copyable
{
public:
  explicit MutexLock(Mutex& m)
    : M(m)
   {
     M.Lock();
   }

  ~MutexLock()
  {
    M.Unlock();
  }

private:
  Mutex& M;
};

//usage:
Mutex m; // guard for data
MutexLock lock(m); //RAII
modify data


2) вам посоветовали исключить ручные Lock\Unlock, потому что это усложняет поддержку кода и может привести к проблемам в случае исключений имхо (хотя в конкретном коде я проблем не увидел)
3) передача this изнутри класса может привести к непредсказуемым результатам и вы это совсем не сразу обнаружите. если класс регистрировали, имея один указатель, то он может не совпадать с this внутри этого класса (представьте множественные и виртуальные наследования). тут можно опираться на некие ID в самом классе (гуиды или автоинкременты), либо ограничить использование. просто об этом не стОит забывать
4) в паттерне observer еще есть такой подход: класс, за которым наблюдают хранит в себе список weak_ptr на обзерверы. не всегда логично, что этот объект должен управлять временем жизни наблюдателя. таким образом алгоритм усложняется тем, что надо иногда из списка удалять мертвые ссылки и Unregister становится ненужным\необязательным.
успехов
Re[2]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 14:06
Оценка:
Спасибо за конструктивные замечания.
Re: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 25.11.10 14:08
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R> void SendNotification(void* pContext)

R> {
R> Lock();
R> CNotificationListenerPtrList vListeners = m_vListeners;
R> Unlock();

Это, конечно, достаточно печально. Выделять память, копировать N памяти, атомарно инкрементировать N счётчиков под мьютексом не айс.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Многопоточный Observer на C++ (практика)
От: uzhas Ниоткуда  
Дата: 25.11.10 14:16
Оценка:
Здравствуйте, uzhas, Вы писали:

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

U>успехов
совсем забыл
5) никогда не забывайте объявлять виртуальный деструктор в интерфейсах
Re[2]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 14:18
Оценка:
Добавлю ссылку на статью по signals2, ее я пока что не осилил, но собираюсь это сделать
http://www.rsdn.ru/article/cpp/signals2.xml
Автор(ы): Григорьев Вячеслав Владимирович
Дата: 06.09.2010
В статье описывается внутреннее строение boost-библиотеки signals2. Описываются архитектурные решения, применённые в ней. Приводятся сигнатуры классов, их назначение. Объясняется взаимодействие различных компонентов при выполнении вызовов пользователя.

Правильно понял, что signals2 впервые появились в мае 2009-го года,
и до этого были лишь signals, которые были не устойчивы к многопоточности?
Есть ли еще какие-нибудь альтернативы?
Re[3]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 14:28
Оценка:
Здравствуйте, Юрий Жмеренецкий, Вы писали:

ЮЖ>Теоритически эти проблемы могут возникнуть при возбуждении std::bad_alloc при вставке в вектор (CNotificationDispatcher::Subscribe) :


Если необходима обработка нехватки памяти, это делается, но не для примера в статье, вы со мной не согласны?

Наши user-level приложения просто падают когда заканчивается память, и создается креш дамп, ибо странно что закончилась память, неправда-ли?
В большинстве случаев это случается из-за нашей внутренней ошибки (например heap corraption или что-то в этом роде).
В таком случае лучше сразу-же увалить программу и пусть сгенерирует креш дамп, который мы проанализируем и пофиксим баг.
Какой смысл от неотпущенного лока если мы получили критическую ошибку?
Re[4]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 25.11.10 14:31
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Наши user-level приложения просто падают когда заканчивается память, и создается креш дамп, ибо странно что закончилась память, неправда-ли?

R>В большинстве случаев это случается из-за нашей внутренней ошибки (например heap corraption или что-то в этом роде).
R>В таком случае лучше сразу-же увалить программу и пусть сгенерирует креш дамп, который мы проанализируем и пофиксим баг.
R>Какой смысл от неотпущенного лока если мы получили критическую ошибку?

Если ты в фотошопе редактируешь большую картинку, и начинаешь применять какой-то сложный фильтр, какой вариант развития событий ты выберешь?
1. Фотошоп падает и теряет твои изменения.
2. Фотошоп откатывает применение фильтра, как будто он не начинался, и выдаёт окошко, что операция прервана из-за нехватки памяти.



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 14:32
Оценка:
Здравствуйте, remark, Вы писали:

R>Это не будет работать. Достаточно в обработчике добавить/удалить несколько подписчиков, как всё крякнется.


Идея была сделать устойчивую финальную версию,
промежуточные версии нужны лишь для объяснения проблеммы, и они не тестировались в различных ситуациях.
Или вы имели в виду финальную версию?
Re[5]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 25.11.10 14:33
Оценка:
Здравствуйте, remark, Вы писали:

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


R>>Наши user-level приложения просто падают когда заканчивается память, и создается креш дамп, ибо странно что закончилась память, неправда-ли?

R>>В большинстве случаев это случается из-за нашей внутренней ошибки (например heap corraption или что-то в этом роде).
R>>В таком случае лучше сразу-же увалить программу и пусть сгенерирует креш дамп, который мы проанализируем и пофиксим баг.
R>>Какой смысл от неотпущенного лока если мы получили критическую ошибку?

R>Если ты в фотошопе редактируешь большую картинку, и начинаешь применять какой-то сложный фильтр, какой вариант развития событий ты выберешь?

R>1. Фотошоп падает и теряет твои изменения.
R>2. Фотошоп откатывает применение фильтра, как будто он не начинался, и выдаёт окошко, что операция прервана из-за нехватки памяти.

И дальше подварианты:
2.1. Фотошоп потом наглухо задедлочится при следующей операции
2.2. Можно будет нормально сохранить изображение, и продолжить работу, когда память освободится
?


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[5]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 14:37
Оценка:
Здравствуйте, remark, Вы писали:

R>Если ты в фотошопе редактируешь большую картинку, и начинаешь применять какой-то сложный фильтр, какой вариант развития событий ты выберешь?

R>1. Фотошоп падает и теряет твои изменения.
R>2. Фотошоп откатывает применение фильтра, как будто он не начинался, и выдаёт окошко, что операция прервана из-за нехватки памяти.

Программы и ситуации бывают разными.
Мне попадался проект в котором сплошь и рядом встречались конструкции try{}catch(...){} которые прятали реальные баги.
программа не падала, но глючила страшно.
Удалось ее нам привести к более менее стабильному состоянию лишь убрав все эти перехватчики.
Re[3]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 25.11.10 14:38
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Идея была сделать устойчивую финальную версию,

R>промежуточные версии нужны лишь для объяснения проблеммы, и они не тестировались в различных ситуациях.
R>Или вы имели в виду финальную версию?

Я имею в виду первую версию. Я хотел сказать, что проблема не столько связана с многопоточностью. Копирование или какой-то другой механизм появился бы у нас ещё в однопоточной версии.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Многопоточный Observer на C++ (практика)
От: Alexander G Украина  
Дата: 25.11.10 14:55
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Правильно понял, что signals2 впервые появились в мае 2009-го года,

R>и до этого были лишь signals, которые были не устойчивы к многопоточности?

Да.

R>Есть ли еще какие-нибудь альтернативы?


Думаю, есть.

Я не интересовался, мне не был нужен многопоточный обсервер. (Вообще, мне кажется, трудно быть уверенным в потокобезопасности, когда не уверен, сколько и каких коллбэков вызываются, даже если сама реализация обсервера безопасна.)
Русский военный корабль идёт ко дну!
Re[2]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 14:55
Оценка:
Здравствуйте, remark, Вы писали:

R>Это, конечно, достаточно печально. Выделять память, копировать N памяти, атомарно инкрементировать N счётчиков под мьютексом не айс.


В драйверах, где мне необходима была оптимизация, я избавился от копирования вектора, но та схема была на порядок сложнее.
Там было что-то вроде связного списка, который перебирается под локом (SpeenLock) и инкрементировался счетчик ссылок, а сам вызов происходил уже без лока.
И удаление ячейки из этого списка происходило тоже под этим локом, еще для чего-то была нужна переменная bRemove, уже не припомню.
Но там еще все это усугублялось необходимостью работать на разных уровнях IRQL (PASSIVE_LEVEL, DISPATCH_LEVEL).

Я хотел предоставить схему, которая будет предельно понятна, т.к. тогда при реализации такой схемы меньше шансов допустить ошибку.
Re[4]: Многопоточный Observer на C++ (практика)
От: uzhas Ниоткуда  
Дата: 25.11.10 14:57
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Если необходима обработка нехватки памяти, это делается, но не для примера в статье, вы со мной не согласны?

мне кажется, что зря вы так относитесь к своим "критикам", вам нужно быть сдержанным и мотать на ус ;=)
некоторые вещи режут глаза опытным разработчикам, потому что у них есть хороший багаж опыта, много книжек всяких прочитали от других профи
даже если вы не переносите исключения, то вы все равно могли бы сделать код более качественным с точки зрения поддержки
в этом вам помог бы механизм RAII
он удобен как при исключениях, так и исключение копи-паста, уменьшение _незначащего_ кода
язык С++ достаточно низкоуровневый и очень часто сложно за деревьями увидеть лес, то есть мелочи отвлекают и мешают понять суть
выработаны практики, который частично решают эти проблемы языка и вам их советуют
даже если вы лично не работаете с исключениями. вы могли бы написать код для клиентов, которые работают с ними
и ваш код не пострадал бы
вы могли заметить, что в некоторых функциях перед return у вас стоит Unlock
это и есть проблема в развивающемся коде (то есть не одноразовом коде студента, а коде, который используется\развивается в компании\команде из большого кол-ва разработчиков)
код в большой компании читают очень много, и он должен быть понятен
можно забыть сделать Unlock и это усложняет поддержку кода
именно поэтому в C использовали идиому single function exit point : http://c2.com/cgi/wiki?SingleFunctionExitPoint (чтобы в самом конце освобождать ресурсы)
в C++ это решили через RAII (инструмент не идеальный, ибо плохо дружит с исключениями, но это отдельная холиворная тема, которые тут уже не раз перемалывали)
попробуйте хотя бы частично всосать те советы, которые вам дают
успехов
Re[4]: Многопоточный Observer на C++ (практика)
От: Юрий Жмеренецкий ICQ 380412032
Дата: 25.11.10 14:59
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

ЮЖ>>Теоритически эти проблемы могут возникнуть при возбуждении std::bad_alloc при вставке в вектор (CNotificationDispatcher::Subscribe) :


R>Если необходима обработка нехватки памяти, это делается, но не для примера в статье, вы со мной не согласны?


Здесь не нужна обработка нехватки памяти. Только RAII для lock/unlock. Как миниму в силу того, что на момент написания неизвестны сценарии использования.

R>Наши user-level приложения просто падают когда заканчивается память, и создается креш дамп, ибо странно что закончилась память, неправда-ли?


Что здесь такого странного?
Re[5]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 15:18
Оценка:
Здравствуйте, Юрий Жмеренецкий, Вы писали:

ЮЖ>Здесь не нужна обработка нехватки памяти. Только RAII для lock/unlock. Как миниму в силу того, что на момент написания неизвестны сценарии использования.


Рои не панацея, и иногда уменьшают читабельность и понимание происходящего.

1) без роев
void SendNotification(void* pContext)
{
    Lock();
    CNotificationListenerPtrList vListeners = m_vListeners;
    Unlock();

    for(size_t i = 0; i < vListeners.size(); ++i)
    {
        vListeners[i].OnNotification(pContext);
    }
}

2) поменяли на рой, но допустили ошибку, она сразу заметна при сравнении с предыдущим кодом?
void SendNotification(void* pContext)
{
    ScopeLocker scopeLocker(&m_cs);
    CNotificationListenerPtrList vListeners = m_vListeners;

    for(size_t i = 0; i < vListeners.size(); ++i)
    {
        vListeners[i].OnNotification(pContext);
    }
}

3) а надо было вот так.
void SendNotification(void* pContext)
{
    CNotificationListenerPtrList vListeners;
    {
        ScopeLocker scopeLocker(&m_cs);
        vListeners = m_vListeners;
    }
    for(size_t i = 0; i < vListeners.size(); ++i)
    {
        vListeners[i].OnNotification(pContext);
    }
}
Re[5]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 15:37
Оценка:
Здравствуйте, uzhas, Вы писали:

U>мне кажется, что зря вы так относитесь к своим "критикам", вам нужно быть сдержанным и мотать на ус ;=)

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

U>некоторые вещи режут глаза опытным разработчикам, потому что у них есть хороший багаж опыта, много книжек всяких прочитали от других профи

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

U>даже если вы не переносите исключения, то вы все равно могли бы сделать код более качественным с точки зрения поддержки

U>в этом вам помог бы механизм RAII
Этот механизм все же "на любителя"

U>код в большой компании читают очень много, и он должен быть понятен

Механизм RAII не улучшает читабельность кода, и понимание его, что более важно для статьи.
Re[6]: Многопоточный Observer на C++ (практика)
От: alexeiz  
Дата: 25.11.10 18:56
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Рои не панацея, и иногда уменьшают читабельность и понимание происходящего.


R>3) а надо было вот так.

R>
R>void SendNotification(void* pContext)
R>{
R>    CNotificationListenerPtrList vListeners;
R>    {
R>        ScopeLocker scopeLocker(&m_cs);
R>        vListeners = m_vListeners;
R>    }
R>    for(size_t i = 0; i < vListeners.size(); ++i)
R>    {
R>        vListeners[i].OnNotification(pContext);
R>    }
R>}
R>


Если тебе мешает дополнительный scope, то можно переписать код так:
R>void SendNotification(void* pContext)
{
    ScopeLocker scopeLocker(&m_cs);
    CNotificationListenerPtrList vListeners = m_vListeners;
    scopeLocker.release();  // or you may call it "unlock"

    for(size_t i = 0; i < vListeners.size(); ++i)
    {
        vListeners[i].OnNotification(pContext);
    }
}
Re[6]: Многопоточный Observer на C++ (практика)
От: Mr.Delphist  
Дата: 25.11.10 19:23
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

U>>код в большой компании читают очень много, и он должен быть понятен

R>Механизм RAII не улучшает читабельность кода, и понимание его, что более важно для статьи.

Однажды мне попался код из проекта одного уважаемого разработчика:

GetResource1();
if (!CheckResource1())
{
  FreeResource1();
  return false;
}

GetResource2();
if (!CheckResource2())
{
  FreeResource2();
  FreeResource1();
  return false;
}

GetResource3();
if (!CheckResource3())
{
  FreeResource3();
  FreeResource2();
  FreeResource1();
  return false;
}

...

FreeResource3();
FreeResource2();
FreeResource1();
return true;


Само собой, это упрощенная схема — реальный код был более развесистый. Память не текла лишь при удачном стечении обстоятельств. После замены на RAII оно стало выглядеть вот так:

GuardResource1();
if (!CheckResource1())
  return false;

GuardResource2();
if (!CheckResource2())
  return false;

GuardResource3();
if (!CheckResource3())
  return false;

...

return true;


Конечно, всех проблем это не решило, но темпы утечек серьезно уменьшились. Пострадала ли читабельность?
Re[3]: Многопоточный Observer на C++ (практика)
От: zaufi Земля  
Дата: 25.11.10 20:19
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

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


R>>Общая идея понятна. Детально код не изучал, но что сразу резануло по глазам — небезопасность кода с точки зрения исключений — повсеместно в коде встречаются фрагменты:

R>>
R>>EnterCriticalSection();
R>>//Какие-то операции
R>>LeaveCriticalSection();
R>>

R>>Что если во время выполнения "каких-то операций" возникнет исключение? Почему бы в данном случае не использовать RAII — например, мьютексы и локи из boost::thread?

R>Вы конечно-же со мной не согласитесь, но я считаю что c++ исключения это зло.

а можно узнать почему??? какие ваши аргументы?

R>Я работаю в команде, где не особо ими пользуются (только при необходимости),

мда бывает неприятность... ;( мои соболезнования

R>а необходимости из под колбека выбрасывать исключение нет никакой.

R>bools и stl может выбросить исключение, но оно возможно лишь когда имеет место ошибка программиста
R>(ну или закончилась память в системе, что тоже критично), тогда нужно падать с креш репортом, дабы не скрывать ошибку.
Re[3]: Многопоточный Observer на C++ (практика)
От: jazzer Россия Skype: enerjazzer
Дата: 26.11.10 06:25
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Здравствуйте, Alexander G, Вы писали:

AG>>ну, когда мне такое понадобится, я скорее посмотрю на boost::signals2
R>Спасибо за наводку, я обязательно обращу внимание на signals2.
Имхо, надо было с этого начать. В диссертациях не зря первая глава всегда — это обзор существующих наработок по теме.

R>Моя статья направлена не на то, чтобы дать готовое решение,

R>она объясняет основные принципы, которые применимы там, где boost бывает недоступен (драйвера, нейтивные приложения и т.п.)
с чего бы это буст был там недоступен? и что такое "нейтивные приложения" в контексте С++?
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[4]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 26.11.10 10:24
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Имхо, надо было с этого начать. В диссертациях не зря первая глава всегда — это обзор существующих наработок по теме.

Спасибо за замечание, я думаю добавить в статью что-то вроде этого:

По сути я не выдумывал новое решение, я использовал готовый шаблон проектирования,
этот же шаблон использовали и создатели boost, но они разрабатывают именно библиотеку, а не решение для частной ситуации.
boost это огромное количество кода, такое количество кода необходимо, если писать универсальную библиотеку, для частного решения может подойти более простой вариант,
и из-за этого он будет более понятным и легко реализуемым.
Вы досконально понимаете как устроена библиотека signal2 в boost?
лично меня пугает количество кода при вызове колбека, вот стек вызовов.
ObserverTest.exe!TrackedHelloWorld::operator()(int n=2)  Line 30    C++
ObserverTest.exe!boost::detail::function::void_function_obj_invoker1<TrackedHelloWorld,void,int>::invoke(boost::detail::function::function_buffer & function_obj_ptr={...}, int a0=2)  Line 154    C++
ObserverTest.exe!boost::function1<void,int>::operator()(int a0=2)  Line 1013 + 0x1a bytes    C++
ObserverTest.exe!boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker::m_invoke(const boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > & connectionBody={...}, const boost::signals2::detail::void_type * __formal=0x00000000)  Line 352    C++
ObserverTest.exe!boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker::operator()(const boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > & connectionBody={...})  Line 340 + 0x14 bytes    C++
ObserverTest.exe!boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >::dereference()  Line 82 + 0x2d bytes    C++
ObserverTest.exe!boost::iterator_core_access::dereference<boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > >(const boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mut& f={...})  Line 517    C++
ObserverTest.exe!boost::iterator_facade<boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,boost::signals2::detail::void_type,boost::single_pass_traversal_tag,boost::signals2::detail::void_type const &,int>::operator*()  Line 634 + 0xe bytes    C++
ObserverTest.exe!boost::signals2::optional_last_value<void>::operator()<boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > >(boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > first={...}, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > last={...})  Line 55 + 0x8 bytes    C++
ObserverTest.exe!boost::signals2::detail::combiner_invoker<void>::operator()<boost::signals2::optional_last_value<void>,boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > >(boost::signals2::optional_last_value<void> & combiner={...}, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > first={...}, boost::signals2::detail::slot_call_iterator_t<boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::slot_invoker,std::list<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> >,std::allocator<boost::shared_ptr<boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > > >::_Iterator<1>,boost::signals2::detail::connection_body<std::pair<enum boost::signals2::detail::slot_meta_group,boost::optional<int> >,boost::signals2::slot1<void,int,boost::function<void __cdecl(int)> >,boost::signals2::mutex> > last={...})  Line 65    C++
ObserverTest.exe!boost::signals2::detail::signal1_impl<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::operator()(int arg1=2)  Line 246 + 0x199 bytes    C++
ObserverTest.exe!boost::signals2::signal1<void,int,boost::signals2::optional_last_value<void>,int,std::less<int>,boost::function<void __cdecl(int)>,boost::function<void __cdecl(boost::signals2::connection const &,int)>,boost::signals2::mutex>::operator()(int arg1=2)  Line 681    C++
ObserverTest.exe!SignalThread(void * pParam=0x00000000)  Line 45    C++


J>с чего бы это буст был там недоступен? и что такое "нейтивные приложения" в контексте С++?

Ну я считаю что многопоточность это уже не совсем контекст c++, возможно я выбрал не совсем ту ветку форума, но более подходящей не нашел.

Native приложение это приложение, которое может запускатья на ранней стадии загрузки виндовс (например консоль восстановления)
виндовый checkdisk так работает, он запускает c:\WINDOWS\system32\autochk.exe на ранней стадии загрузки. autochk.exe это и есть Native приложение.
Native приложение в основном пользуется лишь экспортом из ntdll.dll (недокументированными функциями), и ничего другого ему не доступно.
У нас по началу были большие трудности с CRT и STL, а вы про boost говорите...
Я Windows программист, и никогда не занимался вопросами переносимости кода на другие платформы,
но думаю что и на других платформах существуют схожие проблемы.
Интересно можно ли при написании модулей ядра в Linux пользоваться библиотеками STL и boost?
Re[3]: Многопоточный Observer на C++ (практика)
От: gh2  
Дата: 26.11.10 12:24
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Вы конечно-же со мной не согласитесь, но я считаю что c++ исключения это зло.

R>Я работаю в команде, где не особо ими пользуются (только при необходимости), а необходимости из под колбека выбрасывать исключение нет никакой.
R>bools и stl может выбросить исключение, но оно возможно лишь когда имеет место ошибка программиста
R>(ну или закончилась память в системе, что тоже критично), тогда нужно падать с креш репортом, дабы не скрывать ошибку.

Если класс представлен здесь, значит его предлагается использовать, что в свою очередь влечёт некорректность навязывания своего стиля пользователям.
По-моему, если
1) корректно разрешите проблему безопасности исключений
2) избавитесь от связи (время жизни диспетчера) == (время, проведенное в критической секции)
3) гарантируете отсутствие утечки ресурса "объект-синхронизации" (сейчас это критическая секция)
за приведенный код Вам можно будет сказать спасибо.
Re: :))))))))
От: Ryadovoy  
Дата: 26.11.10 12:44
Оценка:
Здравствуйте, rm822, Вы писали:

R>для 90х это было бы ОК, но на дворе-то 2010й

R>зачем было разводить столько графоманства, когда современными средствами задача решается тривиально?
R>пруф-код, слепленый за 5 ммин

R>
R>struct Subscriber {
R>    void OnSomething(int asContext) { printf("%i\n", asContext); }
R>};

R>int _tmain(int argc, _TCHAR* argv[])
R>{
R>    std::set<Subscriber*> subscribers;
R>    subscribers.insert(new Subscriber());

R>    int context = 7;
R>    task_group g;
R>    g.run( [subscribers,context] { for each(auto s in subscribers) { s->OnSomething(context); }} );
R>    g.wait();

R>    return 0;
R>}


Ваш пример не отображает проблеммную область.

Необходимо было решить задачу следующего типа:
Есть работающий поток, выполняющий какую-то фоновую задачу.
Используя шаблон "наблюдатель" поток периодически отсылает какие-то уведомления.
Необходимо поключится "на ходу" к этому потоку и обрабатывать уведомления.
Притом подключение/отключение должно происходить в отдельном потоке и не вызывать сбоев.
Дополнительно необходимо поддерживать отключение от нотификации при получении этой самой нотификации (прямо из callback-а)

Такую задачу без использования методов синхронизации не решить.
Re[4]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 26.11.10 12:49
Оценка:
Здравствуйте, gh2, Вы писали:

gh2>2) избавитесь от связи (время жизни диспетчера) == (время, проведенное в критической секции)


Можно подробнее, я не понял в чем суть?
Re[5]: Многопоточный Observer на C++ (практика)
От: jazzer Россия Skype: enerjazzer
Дата: 26.11.10 15:34
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Вы досконально понимаете как устроена библиотека signal2 в boost?

R>лично меня пугает количество кода при вызове колбека, вот стек вызовов.

Это стек вызовов отладочной сборки, я так понимаю?

J>>с чего бы это буст был там недоступен? и что такое "нейтивные приложения" в контексте С++?

R>Ну я считаю что многопоточность это уже не совсем контекст c++, возможно я выбрал не совсем ту ветку форума, но более подходящей не нашел.
Если не интересует многопоточность, то есть Boost.Signals первой версии.
У нее проблемы с многопоточностью (как раз с отпиской клиентов), но проще реализация. Так что если не предполагается гонять все это в нескольких потоках (хотя у вас же там критические секции сплошь), то можно использовать ее.

R>Native приложение это приложение, которое может запускатья на ранней стадии загрузки виндовс (например консоль восстановления)

R>виндовый checkdisk так работает, он запускает c:\WINDOWS\system32\autochk.exe на ранней стадии загрузки. autochk.exe это и есть Native приложение.
R>Native приложение в основном пользуется лишь экспортом из ntdll.dll (недокументированными функциями), и ничего другого ему не доступно.
R>У нас по началу были большие трудности с CRT и STL, а вы про boost говорите...
Хм.. Какие именно трудности с STL? CRT — я еще могу понять, но STL — это же библиотека шаблонов Она сама по себе вообще ничего стороннего не зовет, кроме new/delete (и то, почти везде можно отдать свой аллокатор вместо стандартного, и тогда вообще прогулок в CRT не будет).

R>Интересно можно ли при написании модулей ядра в Linux пользоваться библиотеками STL и boost?

С этими вопросами вам лучше в "Низкоуровневое программирование".
Но я видел в англоязычных блогах сообщения, народ юзал буст для драйверов и был очень доволен.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: Многопоточный Observer на C++ (практика)
От: rg45 СССР  
Дата: 26.11.10 15:55
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Вы конечно-же со мной не согласитесь, но я считаю что c++ исключения это зло.

R>...

А откуда такая уверенность в том, что я с этим не соглашусь?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Многопоточный Observer на C++ (практика)
От: baily Россия  
Дата: 26.11.10 16:15
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

Правильно ли я понимаю, что в вашей модели, уже отписавшийся подписчик может получить уведомление?
В самом деле, в методе SendNotification вы делаете копию массива подписок под критической секцией.
После чего у вас некоторый слушатель может отписаться, но все равно получить уведомление.
Это не очень хорошо.
Re[2]: Многопоточный Observer на C++ (практика)
От: baily Россия  
Дата: 26.11.10 16:33
Оценка:
Здравствуйте, baily, Вы писали:

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


B>Правильно ли я понимаю, что в вашей модели, уже отписавшийся подписчик может получить уведомление?

B>В самом деле, в методе SendNotification вы делаете копию массива подписок под критической секцией.
B>После чего у вас некоторый слушатель может отписаться, но все равно получить уведомление.
B>Это не очень хорошо.

Еще мне не нравится, что метод Unsubscribe принимает на вход указатель-подписчика. Получается, что тот, кто отписывает, должен иметь этот указатель. Это не очень удобно. Обычно в таких случаях метод Subscribe возвращает некоторый дескриптор, который потом надо передавать в Unsubscribe. Наример, можно сделать так:


typedef struct HSUBSCRIBE__{int unused; }*HSUBSCRIBE;

class CNotificationDispatcher
{
...
private:
volatile long m_lSubsCounter;
...
};

CNotificationDispatcher::CNotificationDispatcher : m_lSubsCounter(0)
{
...
}

HSUBSCRIBE Subscribe(INotificationListener* pListener)
{
    if( S_OK )
    {
        return (HSUBSCRIBE)InterlockedIncrement( &m_lSubsCounter);
    }
    return (HSUBSCRIBE)0;
}

bool Unsubscribe(HSUBSCRIBE hSubs)
{
...
}
Re[2]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 26.11.10 19:02
Оценка:
Здравствуйте, baily, Вы писали:

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


B>Правильно ли я понимаю, что в вашей модели, уже отписавшийся подписчик может получить уведомление?

B>В самом деле, в методе SendNotification вы делаете копию массива подписок под критической секцией.
B>После чего у вас некоторый слушатель может отписаться, но все равно получить уведомление.
B>Это не очень хорошо.

Можно попробовать ожидать завершение текущего уведомления в функции Unsubscribe, но тогда скорей всего будут зависания при отключении.
Я пока что не нашел подходящее решение с реализацией Unsubscribe, гарантирующей прекращение нотификаций после выхода.
Если есть какие-то идеи по этому поводу, буду рад услышать.
Re[4]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 26.11.10 19:10
Оценка:
Здравствуйте, remark, Вы писали:

R>Это фундаметальная проблема такого подхода.

R>Решить её можно только перейдя к асинхронному обмену сообщениями. А именно с каждым объектом связывается понятие родительского потока, при нотификации родительским потокам рассылаются асинхронные сообщения. При их получении потоки проверяют, не отписан ли ещё каждый конкретный объект, если нет, то вызывается обработчик объекта. Соотв. отписывать можно только из родительского потока.

Это так же избавит от необходимости использования каких-либо мьютексов.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Многопоточный Observer на C++ (практика)
От: baily Россия  
Дата: 26.11.10 20:29
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

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


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


B>>Правильно ли я понимаю, что в вашей модели, уже отписавшийся подписчик может получить уведомление?

B>>В самом деле, в методе SendNotification вы делаете копию массива подписок под критической секцией.
B>>После чего у вас некоторый слушатель может отписаться, но все равно получить уведомление.
B>>Это не очень хорошо.

R>Можно попробовать ожидать завершение текущего уведомления в функции Unsubscribe, но тогда скорей всего будут зависания при отключении.

R>Я пока что не нашел подходящее решение с реализацией Unsubscribe, гарантирующей прекращение нотификаций после выхода.
R>Если есть какие-то идеи по этому поводу, буду рад услышать.

Есть много разных способов. Например, можно связать с каждым подписчиком класс-защелку. К ней должен иметь доступ как подписчик, так и уведомитель.
Защелка открывается в методе Subscribe после добавление подписчика. В методе Unsubscribe она закрывается уведомителем под критической секцией.
Класс подписчик в своем методе OnNotification должен вначале проверять, разрешен ли ему вызов. Если нет, то просто выходить.
Защелкой должен владеть подписчик. Можно просто создать обертку над подписчиком ( в принципе и умный указатель есть обертка ), который владеет и подписчиком
и защелкой. В более сложном варианте можно сделать защелку более сложной, поместив туда еще и событие. При вызове OnNotification, когда защелка открыта,
можно увеличивать счетчик использования защелки в начале и уменьшать в конце. А событие взводить, когда счетчик станет равным нулю. Тогда на событии можно ожидать,
когда подписчик закончит обрабатывать свой OnNotification.

Я в свое время много потрудился над данной задачей. Но у меня требования были пожестче. Там есть немало подводных камней, хотя на вид она кажется простой.
Re[4]: Многопоточный Observer на C++ (практика)
От: baily Россия  
Дата: 26.11.10 20:47
Оценка:
Здравствуйте, remark, Вы писали:

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


R>>Можно попробовать ожидать завершение текущего уведомления в функции Unsubscribe, но тогда скорей всего будут зависания при отключении.

R>>Я пока что не нашел подходящее решение с реализацией Unsubscribe, гарантирующей прекращение нотификаций после выхода.
R>>Если есть какие-то идеи по этому поводу, буду рад услышать.

R>Это фундаметальная проблема такого подхода.

R>Решить её можно только перейдя к асинхронному обмену сообщениями. А именно с каждым объектом связывается понятие родительского потока, при нотификации родительским потокам рассылаются асинхронные сообщения. При их получении потоки проверяют, не отписан ли ещё каждый конкретный объект, если нет, то вызывается обработчик объекта. Соотв. отписывать можно только из родительского потока.

R>


Именно. Если разрешать отписку в методе OnNotification, а это, в принципе, довольно естественное требование, то поток, рассылающий уведомления, должен вызывать уведомления асинхронно.
Re[4]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 26.11.10 21:07
Оценка:
Здравствуйте, baily, Вы писали:

B>Есть много разных способов. Например, можно связать с каждым подписчиком класс-защелку. К ней должен иметь доступ как подписчик, так и уведомитель.

B>Защелка открывается в методе Subscribe после добавление подписчика. В методе Unsubscribe она закрывается уведомителем под критической секцией.
B>Класс подписчик в своем методе OnNotification должен вначале проверять, разрешен ли ему вызов. Если нет, то просто выходить.
B>Защелкой должен владеть подписчик. Можно просто создать обертку над подписчиком ( в принципе и умный указатель есть обертка ), который владеет и подписчиком
B>и защелкой. В более сложном варианте можно сделать защелку более сложной, поместив туда еще и событие. При вызове OnNotification, когда защелка открыта,
B>можно увеличивать счетчик использования защелки в начале и уменьшать в конце. А событие взводить, когда счетчик станет равным нулю. Тогда на событии можно ожидать,
B>когда подписчик закончит обрабатывать свой OnNotification.

Спасибо за идею, надо будет поразмышлять над ней...

B>Я в свое время много потрудился над данной задачей. Но у меня требования были пожестче. Там есть немало подводных камней, хотя на вид она кажется простой.


И это мне нравится больше всего
Re[4]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 26.11.10 21:22
Оценка:
Здравствуйте, remark, Вы писали:

R>Это фундаметальная проблема такого подхода.

R>Решить её можно только перейдя к асинхронному обмену сообщениями. А именно с каждым объектом связывается понятие родительского потока, при нотификации родительским потокам рассылаются асинхронные сообщения. При их получении потоки проверяют, не отписан ли ещё каждый конкретный объект, если нет, то вызывается обработчик объекта. Соотв. отписывать можно только из родительского потока.

Не совсем понял идею родительских потоков (это как?)

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


Re: Многопоточный Observer на C++ (практика)
От: LeonCrew Беларусь  
Дата: 26.11.10 22:53
Оценка:
Хочу спросить у форумчан: на сколько критично автоматическая отписка при уничтожении подписчиков или издателя?
Плюсы:
— программисту не надо постоянно следить за подписками, при уничтожении подписчика, объект сам отпишется от издателя (издатель отпишет/уведомит подписчиков соответственно);
— код бизнес логики не изобилует дополнительными проверками.

Минусы:
— дополнительный расход памяти на дополнительные списки (к каким издателям подписан подписчик), для встраиваемых систем может быть критично;
— расслабляет программиста, вместо продуманного алгоритма с четким временем жизни объектов, чаще пишется код по принципу "заработало и ладно".

Все выше сказанное — мое ИМХО, но жду ваших конструктивных ответов.
Re[5]: Многопоточный Observer на C++ (практика)
От: LeonCrew Беларусь  
Дата: 26.11.10 23:04
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

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


R>Мне кажется не всегда удобно пользоваться асинхронными сообщениями, например если надо у пользователя спросить что-то и в зависимости от ответа продолжить или остановится, с асинхронными сообщениями это будет сделать сложнее. Еще проще для понимания фиксированные состояния системы, если поток ожидает пока пройдет нотификация проще понимать что происходит в системе (проще фиксить баги в некоторых случаях к примеру)

R>Если выбирать между гарантией неполучения сообщения после вызова Unsubscribe в синхронных сообщениях и асинхронными сообщениями я скорее выбрал бы синхронные.

По сути в шаблоне издатель-подписчик, издателю все равно как подписчик отреагировал на полученное сообщение. Если хочется обязательно получить ответ от подписчиков, то я бы сделал обратную связь с подписчиками (установка флага, счетчик принятых сообщений и т.д.). Рассылку сообщений в другой поток сделать асинхронно.
Re: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 26.11.10 23:15
Оценка:
Хочу поблагодарить всех, кто обратил внимание на мой пост и написал свои замечания/советы.
также хочу представить на общий суд отредактированный код, который, надеюсь, учитывает большинство замечаний/предложений.
Отдельное спасибо хочу выразить пользователю remark за идею копирования списка при добавлении-удалении, правда мне предстоит непростая задача объяснить доходно об этой технологии, не уходя далеко от тематики статьи.
в общем вот, версия 1.0.0.2:
namespace Observer
{
    //////////////////////////
    // Subscriber
    //////////////////////////
    typedef unsigned __int64    SubscriberId;
    class CSubscriber
    {
    public:
        virtual ~CSubscriber(){}
        virtual void    OnNotification(void* pContext) = 0;
        SubscriberId    GetSubscriberId()   {return (SubscriberId)this;}
    };
    typedef boost::shared_ptr<CSubscriber> CSubscriberPtr;

    //////////////////////////////////////////////////////////////////////
    // Dispatcher
    ///////////////////////////////////

    class CDispatcher
    {
    private:
        typedef std::vector<CSubscriberPtr>         CSubscriberList;
        typedef boost::shared_ptr<CSubscriberList>  CSubscriberListPtr;
    public:
        SubscriberId Subscribe(CSubscriberPtr pNewSubscriber)
        {
            //Declaration of the next shared pointer before ScopeLocker
            //prevents release of subscribers from under lock
            CSubscriberListPtr pNewSubscriberList(new CSubscriberList());
            //Enter to locked section
            CScopeLocker ScopeLocker(m_Lock);
            if(m_pSubscriberList)
            {
                //Copy existing subscribers
                pNewSubscriberList->assign(m_pSubscriberList->begin(), m_pSubscriberList->end());
            }
            for(size_t i = 0; i < pNewSubscriberList->size(); ++i)
            {
                CSubscriberPtr pCurrSubscriber = (*pNewSubscriberList)[i];
                if(pCurrSubscriber->GetSubscriberId() == pNewSubscriber->GetSubscriberId())
                {
                    return 0;
                }
            }
            //Add new subscriber to new subscriber list
            pNewSubscriberList->push_back(pNewSubscriber);
            //Exchange subscriber lists
            m_pSubscriberList = pNewSubscriberList;
            return pNewSubscriber->GetSubscriberId();
        }
        bool Unsubscribe(SubscriberId id)
        {
            //Declaration of the next shared pointers before ScopeLocker
            //prevents release of subscribers from under lock
            CSubscriberPtr      pSubscriberToRelease;
            CSubscriberListPtr  pNewSubscriberList;
            //Enter to locked section
            CScopeLocker ScopeLocker(m_Lock);
            if(!m_pSubscriberList)
            {
                //No subscribers
                return false;
            }
            pNewSubscriberList = CSubscriberListPtr(new CSubscriberList());
            for(size_t i = 0; i < m_pSubscriberList->size(); ++i)
            {
                CSubscriberPtr pCurrSubscriber = (*m_pSubscriberList)[i];
                if(pCurrSubscriber->GetSubscriberId() == id)
                {
                    pSubscriberToRelease = pCurrSubscriber;
                }
                else
                {
                    pNewSubscriberList->push_back(pCurrSubscriber);
                }
            }
            //Exchange subscriber lists
            m_pSubscriberList = pNewSubscriberList;
            if(!pSubscriberToRelease.get())
            {
                return false;
            }
            return false;
        }
        void SendNotification(void* pContext)
        {
            CSubscriberListPtr pSubscriberList;
            {
                CScopeLocker ScopeLocker(m_Lock);
                if(!m_pSubscriberList)
                {
                    //No subscribers
                    return;
                }
                //Get shared pointer to an existing list of subscribers
                pSubscriberList = m_pSubscriberList;
            }
            //pSubscriberList pointer to copy of subscribers' list
            for(size_t i = 0; i < pSubscriberList->size(); ++i)
            {
                (*pSubscriberList)[i]->OnNotification(pContext);
            }
        }
    private:
        CSubscriberListPtr  m_pSubscriberList;
        CLock               m_Lock;
    };

}; //namespace Observer
Re[2]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 26.11.10 23:22
Оценка:
в функции Unsubscribe я допустил ошибку:
R>        bool Unsubscribe(SubscriberId id)
              ...
R>            if(!pSubscriberToRelease.get())
R>            {
R>                return false;
R>            }
R>            return false; <- должно быть return true;
R>        }
Re[5]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 27.11.10 09:09
Оценка:
Здравствуйте, Ryadovoy, Вы писали:

R>Не совсем понял идею родительских потоков (это как?)


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

R>Мне кажется не всегда удобно пользоваться асинхронными сообщениями, например если надо у пользователя спросить что-то и в зависимости от ответа продолжить или остановится, с асинхронными сообщениями это будет сделать сложнее. Еще проще для понимания фиксированные состояния системы, если поток ожидает пока пройдет нотификация проще понимать что происходит в системе (проще фиксить баги в некоторых случаях к примеру)

R>Если выбирать между гарантией неполучения сообщения после вызова Unsubscribe в синхронных сообщениях и асинхронными сообщениями я скорее выбрал бы синхронные.

Синхронные сообщения безусловно удобнее, но дедлоки это их бич.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 27.11.10 09:20
Оценка:
Здравствуйте, baily, Вы писали:

B>Есть много разных способов. Например, можно связать с каждым подписчиком класс-защелку. К ней должен иметь доступ как подписчик, так и уведомитель.

B>Защелка открывается в методе Subscribe после добавление подписчика. В методе Unsubscribe она закрывается уведомителем под критической секцией.
B>Класс подписчик в своем методе OnNotification должен вначале проверять, разрешен ли ему вызов. Если нет, то просто выходить.

Если он будет просто проверять в начале функции, то мы возвращаемся туда, откуда начали, — может работать нотификация уже отписанного объекта. Ровным счётом ничего не решается.
Какие ешё есть разные способы?


B>Защелкой должен владеть подписчик. Можно просто создать обертку над подписчиком ( в принципе и умный указатель есть обертка ), который владеет и подписчиком

B>и защелкой. В более сложном варианте можно сделать защелку более сложной, поместив туда еще и событие. При вызове OnNotification, когда защелка открыта,
B>можно увеличивать счетчик использования защелки в начале и уменьшать в конце. А событие взводить, когда счетчик станет равным нулю. Тогда на событии можно ожидать,
B>когда подписчик закончит обрабатывать свой OnNotification.

Это потенциальные дедлоки.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[5]: Многопоточный Observer на C++ (практика)
От: baily Россия  
Дата: 27.11.10 09:58
Оценка:
Здравствуйте, remark, Вы писали:

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


B>>Есть много разных способов. Например, можно связать с каждым подписчиком класс-защелку. К ней должен иметь доступ как подписчик, так и уведомитель.

B>>Защелка открывается в методе Subscribe после добавление подписчика. В методе Unsubscribe она закрывается уведомителем под критической секцией.
B>>Класс подписчик в своем методе OnNotification должен вначале проверять, разрешен ли ему вызов. Если нет, то просто выходить.

R>Если он будет просто проверять в начале функции, то мы возвращаемся туда, откуда начали, — может работать нотификация уже отписанного объекта. Ровным счётом ничего не решается.


Я имел ввиду следующее. Надо создать класс-обертку, которая владеет и подписчиком и защелкой.
Она является частью имплементации класса-уведомителя. Обертка тоже имеет метод OnNotification.
Класс-уведомитель хранит массив таких оберток. Когда уведомитель делает рассылку, то он вызывает OnNotification обертки.
Внутри этого метода и проверяется состояние защелки, и только если она открыта, то вызывается OnNotification подписчика.

R>Какие ешё есть разные способы?


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


B>>Защелкой должен владеть подписчик. Можно просто создать обертку над подписчиком ( в принципе и умный указатель есть обертка ), который владеет и подписчиком

B>>и защелкой. В более сложном варианте можно сделать защелку более сложной, поместив туда еще и событие. При вызове OnNotification, когда защелка открыта,
B>>можно увеличивать счетчик использования защелки в начале и уменьшать в конце. А событие взводить, когда счетчик станет равным нулю. Тогда на событии можно ожидать,
B>>когда подписчик закончит обрабатывать свой OnNotification.

R>Это потенциальные дедлоки.


Решение рабочее. Дедлоков при аккуратной работе случаться не должно.
Re[6]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 27.11.10 10:03
Оценка:
Здравствуйте, baily, Вы писали:

R>>Если он будет просто проверять в начале функции, то мы возвращаемся туда, откуда начали, — может работать нотификация уже отписанного объекта. Ровным счётом ничего не решается.


B>Я имел ввиду следующее. Надо создать класс-обертку, которая владеет и подписчиком и защелкой.

B>Она является частью имплементации класса-уведомителя. Обертка тоже имеет метод OnNotification.
B>Класс-уведомитель хранит массив таких оберток. Когда уведомитель делает рассылку, то он вызывает OnNotification обертки.
B>Внутри этого метода и проверяется состояние защелки, и только если она открыта, то вызывается OnNotification подписчика.

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


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[7]: Многопоточный Observer на C++ (практика)
От: remark Россия http://www.1024cores.net/
Дата: 27.11.10 10:07
Оценка:
Здравствуйте, remark, Вы писали:

R>Проверяем состояние защелки — она открыта, начинаем выполнение OnNotification подписчика. В это время другой поток отписывает объект. Первый поток продолжает выполнение OnNotification подписчика, когда объект уже отписан. Вернулись туда, откуда начали. Ничего не решено.


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


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: Многопоточный Observer на C++ (практика)
От: jazzer Россия Skype: enerjazzer
Дата: 27.11.10 13:51
Оценка:
Здравствуйте, remark, Вы писали:

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


R>>Можно попробовать ожидать завершение текущего уведомления в функции Unsubscribe, но тогда скорей всего будут зависания при отключении.

R>>Я пока что не нашел подходящее решение с реализацией Unsubscribe, гарантирующей прекращение нотификаций после выхода.
R>>Если есть какие-то идеи по этому поводу, буду рад услышать.

R>Ещё один вариант решения — трактовать unsubscribe() не как отписку, а как старт асинхронной отписки, а завершение отписки происходит, когда объект получает вызов release(). Т.е. поток вызывает unsubscribe(), этим от говорит, что он хочет инициировать отписку; но никакой код, которы должен отработать только после завершения отписки, он ещё не вызывает. Потом (или возможно сразу) объекту поступает вызов release(), который говорит о том, что отписка завершена, и теперь уже никаких вызовов объекту точно поступать не будет, теперь можно вызывать код, который должен отработать только после завершения отписки.


R>


Я лично именно так бы и сделал, если бы сам писал.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[4]: Многопоточный Observer на C++ (практика)
От: TarasKo Голландия  
Дата: 28.11.10 21:29
Оценка:
gh2>1) корректно разрешите проблему безопасности исключений
gh2>2) избавитесь от связи (время жизни диспетчера) == (время, проведенное в критической секции)
gh2>3) гарантируете отсутствие утечки ресурса "объект-синхронизации" (сейчас это критическая секция)

И как я понимаю получится подмножество библиотеки boost::signals2. Signals2 удобнее ещё тем что не надо реализовывать какой-то интерфейс для создания подписчиков. Подписчиком может быть функция, метод класса или функтор, можно использовать так же bind, что очень удобно в некоторых случаях.
Re[7]: Многопоточный Observer на C++ (практика)
От: baily Россия  
Дата: 29.11.10 09:20
Оценка:
Здравствуйте, remark, Вы писали:

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


R>>>Если он будет просто проверять в начале функции, то мы возвращаемся туда, откуда начали, — может работать нотификация уже отписанного объекта. Ровным счётом ничего не решается.


B>>Я имел ввиду следующее. Надо создать класс-обертку, которая владеет и подписчиком и защелкой.

B>>Она является частью имплементации класса-уведомителя. Обертка тоже имеет метод OnNotification.
B>>Класс-уведомитель хранит массив таких оберток. Когда уведомитель делает рассылку, то он вызывает OnNotification обертки.
B>>Внутри этого метода и проверяется состояние защелки, и только если она открыта, то вызывается OnNotification подписчика.

R>Проверяем состояние защелки — она открыта, начинаем выполнение OnNotification подписчика. В это время другой поток отписывает объект. Первый поток продолжает выполнение OnNotification подписчика, когда объект уже отписан. Вернулись туда, откуда начали. Ничего не решено.


R>


Гм! Согласен. Я поторопился. У меня в коде решение, использующее ту же идею, что у вас в этом
Автор: remark
Дата: 27.11.10
посте. А именно, отписка не происходит, до тех пор, пока не уйдут все лочки с подписчика. Лочка заводится при начале каждой нотификации.
Re[4]: Многопоточный Observer на C++ (практика)
От: COFF  
Дата: 30.11.10 14:55
Оценка:
Здравствуйте, TarasKo, Вы писали:

TK>Лишь один раз я слышал действительно мотивированный отказ от построения обработки ошибок на исключениях, и это google code convention. Они отказываются от исключений в основном потому, что на момент написания code guidelines у них уже написано огромное количество кода с обработкой ошибок на кодах возврата. При этом перед этим идёт честное сравнение плюсов и минусов обеих подходов.


Android NDK, например, не поддерживает исключения и RTTI. Во что превращается портирование кода в случае, если ты их используешь, думаю объяснять не надо ))
Re[2]: Многопоточный Observer на C++ (практика)
От: Feonyf  
Дата: 30.11.10 20:27
Оценка:
Здравствуйте, LeonCrew, Вы писали:

LC>Минусы:

LC>- дополнительный расход памяти на дополнительные списки (к каким издателям подписан подписчик), для встраиваемых систем может быть критично;

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

Поэтому просто делюсь идеей.
Моя строка построения буста:
.\bjam link=static threading=multi runtime-link=static -j %NUMBER_OF_PROCESSORS% --with-filesystem --with-thread --with-date_time address-model=64
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.