Каков наилучший способ синхронизации реализации этого события - PullRequest
1 голос
/ 12 января 2012

Вот моя попытка реализовать событие 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 #.

Ответы [ 3 ]

2 голосов
/ 12 января 2012

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

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

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

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

1 голос
/ 12 января 2012

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

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

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

1 голос
/ 12 января 2012

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

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

РЕДАКТИРОВАТЬ: очень хороший вопрос в вашем комментарии.Честно говоря, я никогда не задумывался о том, много ли у мьютексов накладных расходов при синхронном использовании.Поэтому я собрал этот небольшой тест.


#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 секунды.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...