2012-01-11 3 views
1

Вот моя попытка реализовать событие C++.Каков наилучший способ синхронизации реализации этого события

class Event{ 
    typedef std::tr1::function<void(int&)> CallbackFunction; 
    std::list<CallbackFunction> m_handlers; 

    template<class M> 
    void AddHandler(M& thisPtr, void typename (M::*callback)(int&)) 
    {   
     CallbackFunction bound = std::tr1::bind(callback, &thisPtr, _1); 
     m_handlers.push_back(bound); 
    } 

    void operator()(int& eventArg) 
    { 
     iterate over list... 
     (*iter)(eventArg); 

    }} 

Неполадка здесь - безопасность потока. Если AddHandler и operator() вызываются одновременно, все может сломаться.

Каков наилучший способ синхронизации? Использование мьютекса может привести к снижению производительности. Интересно, что происходит за кулисами boost :: сигналов или события C# в этом случае.

+1

Как часто они звонят в секунду? Почему этот же объект должен быть доступен для всех потоков? Разве они не могут просто отправить сообщение мьютекса, которое получено одним потоком? –

ответ

1

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

Однако, если у вас есть несколько потоков, вызывающих метод operator() на том же объекте, этот мьютекс может быть проблемой. Но без этого, как вы обеспечите, чтобы ваши обратные вызовы были вызваны поточно-безопасным способом? (Я замечаю, что вы передаете целочисленную ссылку и возвращаете пустоту, поэтому я предполагаю, что это не повторные обработчики.)

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

 

#include <stdio.h> 
#include <pthread.h> 

#define USE_PTHREAD_MUTEX 1 

int main(int argc, char * argv[]) { 

pthread_mutex_t mutex; 
pthread_mutex_init(&mutex, NULL); 

long useless_number = 0; 
long counter; 

    for(counter = 0; counter < 100000000; counter++) { 
    #if USE_PTHREAD_MUTEX 
    pthread_mutex_lock(&mutex); 
    #endif 
    useless_number += rand(); 

    #if USE_PTHREAD_MUTEX 
    pthread_mutex_unlock(&mutex); 
    #endif 
    } 

    printf("%ld\n", useless_number); 

} 
 

Я запустил это в своей системе и получил следующие промежутки времени.

С USE_PTHREAD_MUTEX 0 средняя продолжительность работы составляет 1,2 секунды.

С USE_PTHREAD_MUTEX 1 средняя продолжительность работы составляет 2,8 секунды.

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

+0

Как дорого обойтись без мьютекса? скажем, я синхронно называю «AddHandler» 100000s раз. Какое повреждение производительности ожидается здесь? – Leo

+0

Большой вопрос. Я обновил свой ответ с небольшим тестом, потому что мне тоже интересно, какое влияние на производительность. – Tom

+2

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

2

Прежде всего, прежде чем отклонять любую возможность реализации как недостаточно «быстро», вам необходимо определить, каковы требования к производительности. Вы будете запускать эти события тысячи раз в секунду? И если да, то вам действительно нужно будет добавлять обработчиков в контейнер обработчиков все время.

Если ответ на оба этих вопроса по какой-либо причине действительно «да», то вам, возможно, потребуется исследовать блокировочные контейнеры. Это будет означать создание собственного контейнера, а не возможность использования списка stl. Контейнеры с блокировкой будут использовать атомарные свойства (например, InterlockedCompareExchange в окнах, например), чтобы определить атомарно, если конец списка равен NULL или иным образом. Затем они будут использовать аналогичные возможности для добавления к списку. Дополнительные осложнения возникнут, если несколько потоков попытаются добавить обработчики одновременно.

Однако в мире многоядерных машин и переоформления команд и т. Д. Эти подходы могут быть чреваты опасностью. Я лично использую систему событий, не отличающуюся от того, что вы описываете, я использую ее с критическими разделами (которые, по крайней мере, эффективны в Windows), и я не испытываю проблем с производительностью. Но, с другой стороны, ничто не посылается через систему событий не быстрее, чем около 20 Гц или около того.

Как и любой вопрос, связанный с производительностью, ответ всегда будет основываться на ответе на другой вопрос; Где вам нужна ваша работа?

+0

Это событие является общим помощником, поэтому оно оптимально, если эффективно обрабатывает любой шаблон использования. спасибо за обмен опытом, это подталкивает меня к решению critical_section. – Leo

1

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

Вы должны вести подсчет количества обработчиков в классе, и когда вы собираетесь начать итерацию, вы можете с радостью итерации без блокировки, пока не достигнете этого числа.

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

+0

Это замечательное наблюдение. Я могу использовать взаимосвязанные ... чтобы считать это. Но, к сожалению, я, вероятно, добавлю функцию «удалить», чтобы было лучшее решение. Есть предположения? – Leo

+0

Если вы получаете «отмену подписки», вы отмечаете запись флагом, а не перемещаете его из списка. Поток обработчика событий проверяет флаг и может удалить из списка запрещенные элементы, и вам нужно только заблокировать его, если он является последним элементом списка, поскольку только конец списка имеет разницу. – CashCow

Смежные вопросы