Многопоточный 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: Многопоточный Observer на C++ (практика)
От: rg45 СССР  
Дата: 25.11.10 13:18
Оценка: +2
Здравствуйте, Ryadovoy, Вы писали:

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

R>...

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

Что если во время выполнения "каких-то операций" возникнет исключение? Почему бы в данном случае не использовать RAII — например, мьютексы и локи из boost::thread?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Многопоточный Observer на C++ (практика)
От: Alexander G Украина  
Дата: 25.11.10 13:24
Оценка: +1
Здравствуйте, Ryadovoy, Вы писали:

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


ну, когда мне такое понадобится, я скорее посмотрю на boost::signals2
Русский военный корабль идёт ко дну!
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 может выбросить исключение, но оно возможно лишь когда имеет место ошибка программиста
(ну или закончилась память в системе, что тоже критично), тогда нужно падать с креш репортом, дабы не скрывать ошибку.
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:07
Оценка: +1
Здравствуйте, Ryadovoy, Вы писали:

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


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


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
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:13
Оценка: -1
Здравствуйте, remark, Вы писали:

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

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

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

U>успехов
совсем забыл
5) никогда не забывайте объявлять виртуальный деструктор в интерфейсах
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[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++ (практика)
От: 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[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[4]: Многопоточный Observer на C++ (практика)
От: uzhas Ниоткуда  
Дата: 25.11.10 14:37
Оценка: -1
Здравствуйте, remark, Вы писали:

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

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

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

R>
Re[5]: Многопоточный Observer на C++ (практика)
От: Ryadovoy  
Дата: 25.11.10 14:37
Оценка:
Здравствуйте, remark, Вы писали:

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

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

Программы и ситуации бывают разными.
Мне попадался проект в котором сплошь и рядом встречались конструкции try{}catch(...){} которые прятали реальные баги.
программа не падала, но глючила страшно.
Удалось ее нам привести к более менее стабильному состоянию лишь убрав все эти перехватчики.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.