Два метода, которые вы описали, охватывают (концептуально) оба аспекта, однако я думаю, что вы недостаточно объяснили их плюсы и минусы.
Есть одна вещь, о которой вы должны знать, это фактор населения.
- Метод push очень хорош, когда много уведомителей и мало наблюдателей
- Метод Pull отлично подходит, когда мало уведомителей и много наблюдателей
Если у вас много уведомителей и предполагается, что ваш наблюдатель будет перебирать каждый из них, чтобы обнаружить 2 или 3, которые являются dirty
... это не сработает. С другой стороны, если у вас много наблюдателей, и при каждом обновлении вам необходимо уведомлять их всех, то вы, вероятно, обречены, потому что простая итерация по всем из них может убить вашу производительность.
Однако есть одна возможность, о которой вы еще не говорили: объединение двух подходов с другим уровнем косвенности.
- Нажмите каждое изменение на
GlobalObserver
- Пусть каждый наблюдатель проверяет
GlobalObserver
, когда требуется
Это не так просто, потому что каждый наблюдатель должен помнить, когда в последний раз он проверял, получать уведомления только об изменениях, которые он еще не наблюдал. Обычный трюк - использовать эпохи.
Epoch 0 Epoch 1 Epoch 2
event1 event2 ...
... ...
Каждый наблюдатель запоминает следующую эпоху, которую он должен прочитать (когда подписчик подписывается, ему дается текущая эпоха взамен), и читает от этой эпохи до текущей, чтобы узнать обо всех событиях. Как правило, уведомитель не может получить доступ к текущей эпохе, например, вы можете решить переключать эпоху каждый раз, когда поступает запрос на чтение (если текущая эпоха не пуста).
Сложность здесь состоит в том, чтобы знать, когда отбрасывать эпохи (когда они больше не нужны). Это требует подсчета ссылок какого-либо рода. Помните, что GlobalObserver
- это то, что возвращает текущие эпохи объектам. Поэтому мы вводим счетчик для каждой эпохи, который просто подсчитывает, сколько наблюдателей еще не наблюдали эту эпоху (и последующие).
- При подписке мы возвращаем номер эпохи и увеличиваем счетчик этой эпохи
- При опросе мы уменьшаем счетчик опрошенной эпохи, возвращаем текущий номер эпохи и увеличиваем его счетчик
- При отписке мы уменьшаем счетчик эпохи -> следим, чтобы деструктор отписался!
Также возможно комбинировать это с тайм-аутом, регистрируя последний раз, когда мы модифицировали эпоху (т.е. создание следующей), и решая, что через некоторое время мы можем ее сбросить (в этом случае мы возвращаем счетчик и добавить его в следующую эпоху).
Обратите внимание, что схема масштабируется до многопоточности, поскольку одна эпоха доступна для записи (операция выталкивания в стеке), а другие доступны только для чтения (за исключением атомарного счетчика). Можно использовать операции без блокировки, чтобы протолкнуть стек при условии, что не нужно выделять память. Совершенно разумно решить переключить эпоху, когда стек будет завершен.